mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
1143
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
495dce0987 | ||
|
|
a7ff35c229 | ||
|
|
162fc4fb8a | ||
|
|
636ea0a9b2 | ||
|
|
84ed9bc4e7 | ||
|
|
b985d8ff9f | ||
|
|
5a43705e1d | ||
|
|
76af248850 | ||
|
|
8ea7eb4236 | ||
|
|
8f3c3ec1e0 | ||
|
|
8d4bf9c759 | ||
|
|
628757250e | ||
|
|
85094fc695 | ||
|
|
0a88ad77f7 | ||
|
|
4d04f08421 | ||
|
|
4a01971f7d | ||
|
|
977c63dc16 | ||
|
|
2d65d7d6a5 | ||
|
|
4f5ca51cfe | ||
|
|
83208eeab4 | ||
|
|
21674a9f0a | ||
|
|
a18bda14e2 | ||
|
|
37f9670783 | ||
|
|
1745886622 | ||
|
|
bd7361825c | ||
|
|
32af81c132 | ||
|
|
818127349c | ||
|
|
a712bead88 | ||
|
|
aa86a1d12c | ||
|
|
afb58f7155 | ||
|
|
97d5adccd7 | ||
|
|
83cc755388 | ||
|
|
4dc1fe145c | ||
|
|
5e27b23bde | ||
|
|
6011d23a9f | ||
|
|
976d720043 | ||
|
|
86f41e9abc | ||
|
|
8b9fc399fa | ||
|
|
27c75c14e6 | ||
|
|
11d6af8aa0 | ||
|
|
54dc6dcec3 | ||
|
|
c7412d4d5b | ||
|
|
c3817dc175 | ||
|
|
09cedf588e | ||
|
|
66aed89ebd | ||
|
|
16397e7a0f | ||
|
|
5062b483b7 | ||
|
|
b268295c8d | ||
|
|
2b3f23cec9 | ||
|
|
110658ec39 | ||
|
|
e99999ebb6 | ||
|
|
24795e7cba | ||
|
|
32e3386a3e | ||
|
|
7feea8bef3 | ||
|
|
d182434d54 | ||
|
|
0237af0c6e | ||
|
|
1fa36e46c4 | ||
|
|
4d9bae0778 | ||
|
|
9a9b40e400 | ||
|
|
d784b305ef | ||
|
|
7a224b4757 | ||
|
|
76ae704ce4 | ||
|
|
71cd8fa610 | ||
|
|
e9fdcc1285 | ||
|
|
870c995ae2 | ||
|
|
15d7e7c4d1 | ||
|
|
750cdfce0a | ||
|
|
0f128a22b3 | ||
|
|
3296a8f483 | ||
|
|
b77ade2bb9 | ||
|
|
41dc54d736 | ||
|
|
8a0694cb38 | ||
|
|
52c0618bc3 | ||
|
|
e2e1912628 | ||
|
|
36794f1b07 | ||
|
|
ccd8f75b03 | ||
|
|
dc89afe404 | ||
|
|
9db1f3a967 | ||
|
|
a2ab3aede6 | ||
|
|
545fc27d03 | ||
|
|
5bf65aa2b0 | ||
|
|
ebaf07ff1f | ||
|
|
fc95d3e354 | ||
|
|
88dca9337b | ||
|
|
44d9f8d5aa | ||
|
|
f76e9e367d | ||
|
|
69fa4143f6 | ||
|
|
dcbda0c64e | ||
|
|
3db48408af | ||
|
|
3c8edb7294 | ||
|
|
2e2206e0ae | ||
|
|
a43648a24a | ||
|
|
f65f98bf65 | ||
|
|
26d14e15ad | ||
|
|
cee2cc76c4 | ||
|
|
daa33b6301 | ||
|
|
ef52973536 | ||
|
|
6a7bd0803a | ||
|
|
6c70c1c1e6 | ||
|
|
ddc222b8a2 | ||
|
|
5506a582ac | ||
|
|
63d872c98b | ||
|
|
ef9f5dde0d | ||
|
|
e35b7fdb97 | ||
|
|
efbbf382eb | ||
|
|
0b8d00900d | ||
|
|
9077596f25 | ||
|
|
0a8104a32e | ||
|
|
15a0ba4c1e | ||
|
|
d452c8a676 | ||
|
|
fb74b491fa | ||
|
|
2af6e11adc | ||
|
|
a3a98858fd | ||
|
|
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 | ||
|
|
3c46cf2210 | ||
|
|
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 | ||
|
|
edcdfb5937 | ||
|
|
7acbed9637 | ||
|
|
20786fa6fb | ||
|
|
03c750a8b5 | ||
|
|
4da8c6053f | ||
|
|
a43061c61b | ||
|
|
f81efdb38c | ||
|
|
11dc6bac4d | ||
|
|
7d5a886397 | ||
|
|
77aef8179d | ||
|
|
db34ded0a1 | ||
|
|
8a7a92a9b9 | ||
|
|
3eda32f9d7 | ||
|
|
69e7c8ce71 | ||
|
|
c18c193ea7 | ||
|
|
82e294e3ef | ||
|
|
ccf3701011 | ||
|
|
79a8758448 | ||
|
|
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 | ||
|
|
8c1194283b | ||
|
|
28397eb7a9 | ||
|
|
8ebd7da9a8 | ||
|
|
a511af6fd2 | ||
|
|
97a22bb8eb | ||
|
|
342ed489f3 | ||
|
|
6b88a01fad | ||
|
|
5e5e0df4ec | ||
|
|
c02c40cb65 | ||
|
|
be222e3ffa | ||
|
|
7747df7b96 | ||
|
|
efe747b8aa | ||
|
|
4421ec1698 | ||
|
|
7815b67a8a | ||
|
|
84aed0720f | ||
|
|
b212e0fb99 | ||
|
|
b3ab5ea62e | ||
|
|
9d4486f906 | ||
|
|
a79b397d17 | ||
|
|
46a33851fd | ||
|
|
1da52a3fec | ||
|
|
12065fdb99 | ||
|
|
d5c1daaa48 | ||
|
|
be65f1ce58 | ||
|
|
163c11b60c | ||
|
|
47a280cc16 | ||
|
|
ba34df3b4b | ||
|
|
51473ec137 | ||
|
|
2da61c1e54 | ||
|
|
e23f040962 | ||
|
|
f370e96058 | ||
|
|
ec88f9bd02 | ||
|
|
bce0c8de06 | ||
|
|
44ceccff12 |
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,136 +2,90 @@ name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
servers-build:
|
||||
name: Release Build Server
|
||||
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.21
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
|
||||
- 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@v4
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Make
|
||||
run: make servers
|
||||
|
||||
- name: Server Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
name: servers
|
||||
path: ./sliver-server_*
|
||||
fetch-depth: 0
|
||||
|
||||
clients-build:
|
||||
name: Release Build Client
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
- name: Find Semver Tag
|
||||
id: tag
|
||||
run: |
|
||||
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"
|
||||
|
||||
release:
|
||||
name: Release
|
||||
needs: tag-check
|
||||
if: needs.tag-check.outputs.tag != ''
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Make
|
||||
run: make clients
|
||||
|
||||
- name: Client Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
name: clients
|
||||
path: ./sliver-client_*
|
||||
fetch-depth: 0
|
||||
|
||||
tagged-release:
|
||||
needs: [servers-build, clients-build]
|
||||
|
||||
name: "Tagged Release"
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- run: mkdir -p ./builds
|
||||
- uses: actions/download-artifact@v3
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
path: ./builds
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Extract Artifacts
|
||||
run: |
|
||||
mkdir -p ./artifacts
|
||||
cp ./builds/clients/* ./artifacts
|
||||
cp ./builds/servers/* ./artifacts
|
||||
- name: Install Minisign
|
||||
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign
|
||||
|
||||
- name: GPG Key(s)
|
||||
- name: Build Clients
|
||||
run: |
|
||||
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
make clients
|
||||
mkdir -p artifacts
|
||||
mv sliver-client_* artifacts/
|
||||
|
||||
# Linux
|
||||
- name: Linux 386 (Client Only)
|
||||
- name: Build Servers
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-386.sig --detach-sig ./artifacts/sliver-client_linux-386
|
||||
- name: Linux AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux-amd64.sig --detach-sig ./artifacts/sliver-server_linux-amd64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-amd64.sig --detach-sig ./artifacts/sliver-client_linux-amd64
|
||||
- name: Linux ARM64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux-arm64.sig --detach-sig ./artifacts/sliver-server_linux-arm64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-arm64.sig --detach-sig ./artifacts/sliver-client_linux-arm64
|
||||
make servers
|
||||
mv sliver-server_* artifacts/
|
||||
|
||||
# Windows
|
||||
- name: Windows 386 (Client Only)
|
||||
- name: Sign Artifacts
|
||||
env:
|
||||
MINISIGN_KEY: ${{ secrets.MINISIGN_SLIVER_RELEASE_KEY }}
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-386.sig --detach-sig ./artifacts/sliver-client_windows-386.exe
|
||||
- name: Windows AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows-amd64.sig --detach-sig ./artifacts/sliver-server_windows-amd64.exe
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-amd64.sig --detach-sig ./artifacts/sliver-client_windows-amd64.exe
|
||||
- name: Windows ARM64 (Client Only)
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-arm64.sig --detach-sig ./artifacts/sliver-client_windows-arm64.exe
|
||||
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
|
||||
|
||||
# MacOS
|
||||
- name: MacOS AMD64
|
||||
- name: Create Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-amd64.sig --detach-sig ./artifacts/sliver-server_macos-amd64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-amd64.sig --detach-sig ./artifacts/sliver-client_macos-amd64
|
||||
- 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
|
||||
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
|
||||
|
||||
# FreeBSD
|
||||
- name: FreeBSD AMD64 (Client Only)
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_freebsd-amd64.sig --detach-sig ./artifacts/sliver-client_freebsd-amd64.exe
|
||||
- name: FreeBSD ARM64 (Client Only)
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_freebsd-arm64.sig --detach-sig ./artifacts/sliver-client_freebsd-arm64.exe
|
||||
|
||||
- name: "Publish Release"
|
||||
uses: "bishopfox/action-gh-release@v1"
|
||||
with:
|
||||
files: |
|
||||
./artifacts/*
|
||||
- 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.21
|
||||
uses: actions/setup-go@v4
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
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.
|
||||
@@ -36,10 +36,10 @@ jobs:
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- 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
|
||||
@@ -48,6 +48,6 @@ jobs:
|
||||
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
|
||||
@@ -3,6 +3,14 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "Makefile"
|
||||
- "**/*.go"
|
||||
- "**/*.c"
|
||||
- "**/*.h"
|
||||
- "**/*.sh"
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
@@ -13,52 +21,62 @@ jobs:
|
||||
linux-windows-build-test:
|
||||
name: Linux & Windows Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v4
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- 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
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Make Linux (amd64)
|
||||
run: make linux-amd64
|
||||
|
||||
- name: Test Linux
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
- 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: Determine go-tests flags
|
||||
id: go-tests-flags
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_sha="${{ github.event.pull_request.base.sha }}"
|
||||
head_sha="${{ github.sha }}"
|
||||
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
|
||||
echo "args=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Test Linux
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
|
||||
|
||||
|
||||
macos-build-test:
|
||||
name: MacOS Test
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v4
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
@@ -66,27 +84,37 @@ jobs:
|
||||
- name: Make MacOS (amd64)
|
||||
run: make macos-amd64
|
||||
|
||||
- name: Test MacOS (amd64)
|
||||
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
|
||||
|
||||
- name: Make MacOS (arm64)
|
||||
run: make macos-arm64
|
||||
|
||||
- name: Determine go-tests flags
|
||||
id: go-tests-flags
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_sha="${{ github.event.pull_request.base.sha }}"
|
||||
head_sha="${{ github.sha }}"
|
||||
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
|
||||
echo "args=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Test MacOS
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
|
||||
|
||||
clients-build:
|
||||
name: Clients Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v4
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
/.devcontainer
|
||||
/.idea
|
||||
|
||||
/artifacts
|
||||
/artifacts/**
|
||||
|
||||
*.rc
|
||||
|
||||
/Dockerfile.*
|
||||
/*.sdk.tar.xz
|
||||
|
||||
@@ -45,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.
|
||||
+86
-29
@@ -1,6 +1,16 @@
|
||||
#
|
||||
# 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 .
|
||||
#
|
||||
|
||||
# STAGE: base
|
||||
## Compiles Sliver for use
|
||||
FROM --platform=linux/amd64 golang:1.21.4 as base
|
||||
FROM golang:latest AS base
|
||||
|
||||
### Base packages
|
||||
RUN apt-get update --fix-missing && apt-get -y install \
|
||||
@@ -11,57 +21,104 @@ RUN groupadd -g 999 sliver && useradd -r -u 999 -g sliver sliver
|
||||
RUN mkdir -p /home/sliver/ && chown -R sliver:sliver /home/sliver
|
||||
|
||||
### 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
|
||||
RUN make
|
||||
RUN make
|
||||
RUN cp -vv sliver-server /opt/sliver-server
|
||||
|
||||
# STAGE: test
|
||||
## Run unit tests against the compiled instance
|
||||
## Use `--target test` in the docker build command to run this stage
|
||||
FROM --platform=linux/amd64 base as test
|
||||
FROM base AS test
|
||||
|
||||
### Install testing packages
|
||||
RUN apt-get update --fix-missing && apt-get -y install \
|
||||
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 xsel postgresql \
|
||||
postgresql-contrib postgresql-client libpq-dev \
|
||||
libapr1 libaprutil1 libsvn1 \
|
||||
libpcap-dev libsqlite3-dev libgmp3-dev \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 \
|
||||
nasm gcc-multilib
|
||||
ARG GO_TESTS_FLAGS=""
|
||||
ENV GO_TESTS_FLAGS="${GO_TESTS_FLAGS}"
|
||||
|
||||
### Install MSF for testing
|
||||
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'
|
||||
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
|
||||
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh ${GO_TESTS_FLAGS}
|
||||
|
||||
# STAGE: production
|
||||
## Final dockerized form of Sliver
|
||||
FROM --platform=linux/amd64 debian:bookworm-slim as production
|
||||
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
|
||||
|
||||
### 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
|
||||
|
||||
### 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
|
||||
|
||||
USER sliver
|
||||
WORKDIR /home/sliver/
|
||||
VOLUME [ "/home/sliver/.sliver" ]
|
||||
ENTRYPOINT [ "/opt/sliver-server" ]
|
||||
@@ -16,49 +16,32 @@ 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 = 21
|
||||
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
|
||||
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
|
||||
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)
|
||||
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
|
||||
|
||||
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 $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
# Debug builds shouldn'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_PUBLIC_KEY) \
|
||||
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
@@ -83,7 +66,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
|
||||
|
||||
@@ -91,12 +76,6 @@ 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_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
endif
|
||||
@@ -105,9 +84,10 @@ endif
|
||||
# Targets
|
||||
#
|
||||
.PHONY: default
|
||||
default: clean .downloaded_assets validate-go-version
|
||||
$(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) CGO_ENABLED=0 $(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
|
||||
|
||||
# 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.
|
||||
@@ -154,8 +134,9 @@ clients: clean .downloaded_assets validate-go-version
|
||||
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:
|
||||
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
|
||||
@@ -199,5 +180,5 @@ clean:
|
||||
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
|
||||
@@ -6,22 +6,16 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
|
||||
|
||||
[](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [](https://goreportcard.com/report/github.com/BishopFox/sliver) [](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
# v1.6.0 / `master`
|
||||
|
||||
**NOTE:** You are looking the latest master branch of Sliver v1.6.0; PRs should target this branch starting on 6/1/2023 ('MURICA format). However, this branch is NOT RECOMMENDED for production use yet. Please use release tagged versions for the best experience.
|
||||
|
||||
For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x/master` branch](https://github.com/BishopFox/sliver/tree/v1.5.x/master).
|
||||
|
||||
### 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)
|
||||
- [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
|
||||
@@ -31,7 +25,7 @@ For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x
|
||||
|
||||
### 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
|
||||
|
||||
@@ -39,16 +33,13 @@ 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
|
||||
|
||||
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
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
|
||||
|
||||
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 --target test .")
|
||||
if status == 0:
|
||||
print("\n\n" + INFO + "Build successful")
|
||||
|
||||
|
||||
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.
|
||||
+58
-19
@@ -27,7 +27,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
armoryConfigFileName = "armories.json"
|
||||
ArmoryConfigFileName = "armories.json"
|
||||
DefaultArmoryName = "Default"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -36,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,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -48,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 := os.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 {
|
||||
|
||||
+23
-6
@@ -34,12 +34,19 @@ const (
|
||||
SliverClientDirName = ".sliver-client"
|
||||
|
||||
versionFileName = "version"
|
||||
envVarName = "SLIVER_CLIENT_ROOT_DIR"
|
||||
)
|
||||
|
||||
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
|
||||
func GetRootAppDir() string {
|
||||
user, _ := user.Current()
|
||||
dir := filepath.Join(user.HomeDir, SliverClientDirName)
|
||||
value := os.Getenv(envVarName)
|
||||
var dir string
|
||||
if len(value) == 0 {
|
||||
user, _ := user.Current()
|
||||
dir = filepath.Join(user.HomeDir, SliverClientDirName)
|
||||
} else {
|
||||
dir = value
|
||||
}
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
@@ -73,6 +80,18 @@ func GetConsoleLogsDir() string {
|
||||
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 := os.ReadFile(filepath.Join(appDir, versionFileName))
|
||||
@@ -96,7 +115,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) 2026 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.`)
|
||||
@@ -104,7 +123,5 @@ under certain conditions; type 'licenses' for details.`)
|
||||
}
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(appDir, settingsFileName)); os.IsNotExist(err) {
|
||||
SaveSettings(nil)
|
||||
}
|
||||
_, _ = LoadSettings()
|
||||
}
|
||||
+12
-25
@@ -35,7 +35,7 @@ type NameValueProbability struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Probability int `json:"probability"`
|
||||
Methods []string
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
// HTTPC2ImplantConfig - Implant configuration options
|
||||
@@ -57,30 +57,17 @@ type HTTPC2ImplantConfig struct {
|
||||
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"`
|
||||
|
||||
MaxFiles int `json:"max_files"`
|
||||
MinFiles int `json:"min_files"`
|
||||
MaxPaths int `json:"max_paths"`
|
||||
MinPaths int `json:"min_paths"`
|
||||
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"`
|
||||
|
||||
// Stager files and paths
|
||||
StagerFileExt string `json:"stager_file_ext"`
|
||||
StagerFiles []string `json:"stager_files"`
|
||||
StagerPaths []string `json:"stager_paths"`
|
||||
|
||||
// Poll files and paths
|
||||
PollFileExt string `json:"poll_file_ext"`
|
||||
PollFiles []string `json:"poll_files"`
|
||||
PollPaths []string `json:"poll_paths"`
|
||||
|
||||
// Session files and paths
|
||||
StartSessionFileExt string `json:"start_session_file_ext"`
|
||||
SessionFileExt string `json:"session_file_ext"`
|
||||
SessionFiles []string `json:"session_files"`
|
||||
SessionPaths []string `json:"session_paths"`
|
||||
|
||||
// Close session files and paths
|
||||
CloseFileExt string `json:"close_file_ext"`
|
||||
CloseFiles []string `json:"close_files"`
|
||||
ClosePaths []string `json:"close_paths"`
|
||||
Extensions []string `json:"extensions"`
|
||||
Files []string `json:"files"`
|
||||
Paths []string `json:"paths"`
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func legacyBackupPath(legacyPath string) string {
|
||||
dir := filepath.Dir(legacyPath)
|
||||
base := filepath.Base(legacyPath)
|
||||
name := strings.TrimSuffix(base, filepath.Ext(base))
|
||||
return filepath.Join(dir, "."+name+".json-old")
|
||||
}
|
||||
|
||||
func renameLegacyConfig(legacyPath string) error {
|
||||
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
backupPath := legacyBackupPath(legacyPath)
|
||||
if _, err := os.Stat(backupPath); err == nil {
|
||||
if err := os.Remove(backupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.Rename(legacyPath, backupPath)
|
||||
}
|
||||
+39
-16
@@ -22,35 +22,58 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
settingsFileName = "tui-settings.json"
|
||||
settingsFileName = "tui-settings.yaml"
|
||||
settingsLegacyFileName = "tui-settings.json"
|
||||
)
|
||||
|
||||
// ClientSettings - Client JSON config
|
||||
type ClientSettings struct {
|
||||
TableStyle string `json:"tables"`
|
||||
AutoAdult bool `json:"autoadult"`
|
||||
BeaconAutoResults bool `json:"beacon_autoresults"`
|
||||
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"`
|
||||
TableStyle string `json:"tables" yaml:"tables"`
|
||||
AutoAdult bool `json:"autoadult" yaml:"autoadult"`
|
||||
BeaconAutoResults bool `json:"beacon_autoresults" yaml:"beacon_autoresults"`
|
||||
SmallTermWidth int `json:"small_term_width" yaml:"small_term_width"`
|
||||
AlwaysOverflow bool `json:"always_overflow" yaml:"always_overflow"`
|
||||
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
|
||||
UserConnect bool `json:"user_connect" yaml:"user_connect"`
|
||||
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
|
||||
}
|
||||
|
||||
// LoadSettings - Load the client settings from disk
|
||||
func LoadSettings() (*ClientSettings, error) {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
data, err := os.ReadFile(filepath.Join(rootDir, settingsFileName))
|
||||
if err != nil {
|
||||
settingsPath := filepath.Join(rootDir, settingsFileName)
|
||||
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
|
||||
settings := defaultSettings()
|
||||
migratedLegacy := false
|
||||
|
||||
data, err := os.ReadFile(settingsPath)
|
||||
if err == nil {
|
||||
if err = yaml.Unmarshal(data, settings); err != nil {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return defaultSettings(), err
|
||||
} else if data, err = os.ReadFile(legacyPath); err == nil {
|
||||
if err = json.Unmarshal(data, settings); err != nil {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
migratedLegacy = true
|
||||
} else if !os.IsNotExist(err) {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
settings := defaultSettings()
|
||||
err = json.Unmarshal(data, settings)
|
||||
if err != nil {
|
||||
return defaultSettings(), err
|
||||
|
||||
if err := SaveSettings(settings); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
if migratedLegacy {
|
||||
if err := renameLegacyConfig(legacyPath); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
@@ -73,7 +96,7 @@ func SaveSettings(settings *ClientSettings) error {
|
||||
if settings == nil {
|
||||
settings = defaultSettings()
|
||||
}
|
||||
data, err := json.MarshalIndent(settings, "", " ")
|
||||
data, err := yaml.Marshal(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadSettingsParsesYAML(t *testing.T) {
|
||||
t.Setenv(envVarName, t.TempDir())
|
||||
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
settingsPath := filepath.Join(rootDir, settingsFileName)
|
||||
|
||||
data := []byte(`tables: "Compact"
|
||||
autoadult: true
|
||||
beacon_autoresults: false
|
||||
small_term_width: 120
|
||||
always_overflow: true
|
||||
vim_mode: true
|
||||
user_connect: true
|
||||
console_logs: false
|
||||
`)
|
||||
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
|
||||
t.Fatalf("failed to write yaml settings: %v", err)
|
||||
}
|
||||
|
||||
settings, err := LoadSettings()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if settings.TableStyle != "Compact" {
|
||||
t.Fatalf("expected TableStyle %q, got %q", "Compact", settings.TableStyle)
|
||||
}
|
||||
if !settings.AutoAdult {
|
||||
t.Fatalf("expected AutoAdult true")
|
||||
}
|
||||
if settings.BeaconAutoResults {
|
||||
t.Fatalf("expected BeaconAutoResults false")
|
||||
}
|
||||
if settings.SmallTermWidth != 120 {
|
||||
t.Fatalf("expected SmallTermWidth %d, got %d", 120, settings.SmallTermWidth)
|
||||
}
|
||||
if !settings.AlwaysOverflow {
|
||||
t.Fatalf("expected AlwaysOverflow true")
|
||||
}
|
||||
if !settings.VimMode {
|
||||
t.Fatalf("expected VimMode true")
|
||||
}
|
||||
if !settings.UserConnect {
|
||||
t.Fatalf("expected UserConnect true")
|
||||
}
|
||||
if settings.ConsoleLogs {
|
||||
t.Fatalf("expected ConsoleLogs false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
|
||||
t.Setenv(envVarName, t.TempDir())
|
||||
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
|
||||
|
||||
legacy := &ClientSettings{
|
||||
TableStyle: "Legacy",
|
||||
AutoAdult: true,
|
||||
BeaconAutoResults: false,
|
||||
SmallTermWidth: 200,
|
||||
AlwaysOverflow: true,
|
||||
VimMode: true,
|
||||
UserConnect: true,
|
||||
ConsoleLogs: false,
|
||||
}
|
||||
data, err := json.MarshalIndent(legacy, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal legacy settings: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(legacyPath, data, 0o600); err != nil {
|
||||
t.Fatalf("failed to write legacy settings: %v", err)
|
||||
}
|
||||
|
||||
settings, err := LoadSettings()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if settings.TableStyle != "Legacy" {
|
||||
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||
t.Fatalf("expected yaml settings to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected legacy settings to be renamed: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
|
||||
t.Fatalf("expected legacy backup to exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSettingsWritesDefault(t *testing.T) {
|
||||
t.Setenv(envVarName, t.TempDir())
|
||||
|
||||
settings, err := LoadSettings()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if settings.TableStyle != "SliverDefault" {
|
||||
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
|
||||
}
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||
t.Fatalf("expected default settings to be written: %v", 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.
|
||||
+13
-8
@@ -24,20 +24,17 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
)
|
||||
|
||||
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, 0o600)
|
||||
@@ -49,7 +46,12 @@ 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
|
||||
@@ -68,6 +70,9 @@ func init() {
|
||||
// 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
|
||||
@@ -88,10 +93,10 @@ var rootCmd = &cobra.Command{
|
||||
Long: ``,
|
||||
}
|
||||
|
||||
// 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
-25
@@ -23,8 +23,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
)
|
||||
|
||||
func selectConfig() *assets.ClientConfig {
|
||||
@@ -40,32 +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]
|
||||
}
|
||||
+52
-14
@@ -1,37 +1,54 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
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.SliverConsoleClient) *cobra.Command {
|
||||
// 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.SliverConsoleClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
|
||||
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
|
||||
var ln *grpc.ClientConn
|
||||
|
||||
pre = func(_ *cobra.Command, _ []string) error {
|
||||
appDir := assets.GetRootAppDir()
|
||||
logFile := initLogging(appDir)
|
||||
defer logFile.Close()
|
||||
pre = func(cmd *cobra.Command, _ []string) error {
|
||||
|
||||
configs := assets.GetConfigs()
|
||||
if len(configs) == 0 {
|
||||
@@ -43,13 +60,18 @@ func consoleRunnerCmd(con *console.SliverConsoleClient, run bool) (pre, post fun
|
||||
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
|
||||
var err error
|
||||
|
||||
rpc, ln, err = transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
@@ -57,7 +79,10 @@ func consoleRunnerCmd(con *console.SliverConsoleClient, run bool) (pre, post fun
|
||||
return nil
|
||||
}
|
||||
|
||||
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run)
|
||||
// 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
|
||||
@@ -71,3 +96,16 @@ func consoleRunnerCmd(con *console.SliverConsoleClient, run bool) (pre, post fun
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-9
@@ -1,19 +1,36 @@
|
||||
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/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"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.SliverConsoleClient) *cobra.Command {
|
||||
func implantCmd(con *console.SliverClient) *cobra.Command {
|
||||
con.IsCLI = true
|
||||
|
||||
makeCommands := command.SliverCommands(con)
|
||||
@@ -25,7 +42,7 @@ func implantCmd(con *console.SliverConsoleClient) *cobra.Command {
|
||||
implantFlags.StringP("use", "s", "", "interact with a session")
|
||||
cmd.Flags().AddFlagSet(implantFlags)
|
||||
|
||||
// Prerunners (console setup, connection, etc)
|
||||
// Pre-runners (console setup, connection, etc)
|
||||
cmd.PersistentPreRunE, cmd.PersistentPostRunE = makeRunners(cmd, con)
|
||||
|
||||
// Completions
|
||||
@@ -34,7 +51,7 @@ func implantCmd(con *console.SliverConsoleClient) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeRunners(implantCmd *cobra.Command, con *console.SliverConsoleClient) (pre, post func(cmd *cobra.Command, args []string) error) {
|
||||
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,
|
||||
@@ -59,7 +76,7 @@ func makeRunners(implantCmd *cobra.Command, con *console.SliverConsoleClient) (p
|
||||
return pre, closeConsole
|
||||
}
|
||||
|
||||
func makeCompleters(cmd *cobra.Command, con *console.SliverConsoleClient) {
|
||||
func makeCompleters(cmd *cobra.Command, con *console.SliverClient) {
|
||||
comps := carapace.Gen(cmd)
|
||||
|
||||
comps.PreRun(func(cmd *cobra.Command, args []string) {
|
||||
@@ -67,7 +84,7 @@ func makeCompleters(cmd *cobra.Command, con *console.SliverConsoleClient) {
|
||||
})
|
||||
|
||||
// Bind completers to flags (wrap them to use the same pre-runners)
|
||||
command.FlagComps(cmd, func(comp *carapace.ActionMap) {
|
||||
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)
|
||||
|
||||
@@ -22,10 +22,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
)
|
||||
|
||||
func importCmd() *cobra.Command {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -21,9 +21,8 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdVersion = &cobra.Command{
|
||||
|
||||
+64
-11
@@ -1,14 +1,67 @@
|
||||
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.
|
||||
- `edit/` – Implements the 'edit' command 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.
|
||||
@@ -27,15 +27,14 @@ import (
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
|
||||
"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(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) error {
|
||||
// AliasesCmd - The alias command.
|
||||
func AliasesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) error {
|
||||
if 0 < len(loadedAliases) {
|
||||
PrintAliases(con)
|
||||
} else {
|
||||
@@ -45,8 +44,8 @@ func AliasesCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []str
|
||||
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{
|
||||
@@ -88,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) {
|
||||
@@ -129,7 +128,7 @@ func getInstalledManifests() map[string]*AliasManifest {
|
||||
return installedManifests
|
||||
}
|
||||
|
||||
// AliasCommandNameCompleter - Completer for installed extensions command names
|
||||
// AliasCommandNameCompleter - Completer for installed extensions command names.
|
||||
func AliasCompleter() carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
results := []string{}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
@@ -24,31 +24,30 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
|
||||
// AliasesInstallCmd - Install an alias
|
||||
func AliasesInstallCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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) {
|
||||
// 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)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -140,7 +143,7 @@ func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.Sl
|
||||
}
|
||||
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("Failed to install file: %s\n", err)
|
||||
forceRemoveAll(installPath)
|
||||
@@ -152,7 +155,7 @@ func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.Sl
|
||||
return &installPath
|
||||
}
|
||||
|
||||
func installArtifact(aliasGzFilePath string, installPath, artifactPath string, con *console.SliverConsoleClient) error {
|
||||
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
|
||||
|
||||
+279
-63
@@ -27,19 +27,20 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
app "github.com/reeflective/console"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"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/carapace-sh/carapace"
|
||||
app "github.com/reeflective/console"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -53,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 *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"`
|
||||
@@ -86,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) {
|
||||
@@ -104,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 {
|
||||
@@ -124,7 +139,7 @@ func (a *AliasManifest) getFileForTarget(cmdName string, targetOS string, target
|
||||
}
|
||||
|
||||
// AliasesLoadCmd - Locally load a alias into the Sliver shell.
|
||||
func AliasesLoadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
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)
|
||||
@@ -135,8 +150,8 @@ func AliasesLoadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args [
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAlias - Load an alias into the Sliver shell from a given directory
|
||||
func LoadAlias(manifestPath string, cmd *cobra.Command, 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)
|
||||
@@ -155,15 +170,60 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverConso
|
||||
}
|
||||
aliasManifest.RootPath = filepath.Dir(manifestPath)
|
||||
|
||||
// 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("]")
|
||||
}
|
||||
}
|
||||
|
||||
// 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]"
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
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)
|
||||
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 := &cobra.Command{
|
||||
Use: aliasManifest.CommandName,
|
||||
Use: usage.String(),
|
||||
Short: helpMsg,
|
||||
Long: longHelpMsg,
|
||||
Long: help.FormatHelpTmpl(longHelp.String()),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runAliasCommand(cmd, con, args)
|
||||
},
|
||||
@@ -179,9 +239,9 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverConso
|
||||
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)")
|
||||
f.StringP("runtime", "r", "v4.0.30319", "Runtime to use for running the assembly")
|
||||
f.BoolP("amsi-bypass", "M", false, "Bypass AMSI on Windows")
|
||||
f.BoolP("etw-bypass", "E", false, "Bypass ETW on Windows")
|
||||
addAliasCmd.Flags().AddFlagSet(f)
|
||||
}
|
||||
|
||||
@@ -193,6 +253,10 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverConso
|
||||
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
|
||||
@@ -205,7 +269,7 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverConso
|
||||
return aliasManifest, nil
|
||||
}
|
||||
|
||||
// ParseAliasManifest - Parse an alias manifest
|
||||
// ParseAliasManifest - Parse an alias manifest.
|
||||
func ParseAliasManifest(data []byte) (*AliasManifest, error) {
|
||||
// parse it
|
||||
alias := &AliasManifest{}
|
||||
@@ -238,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(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
@@ -268,20 +339,20 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
return
|
||||
}
|
||||
// args := ctx.Args.StringList("arguments")
|
||||
var extArgs string
|
||||
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, _ := 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
|
||||
@@ -301,10 +372,9 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
} 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
|
||||
}
|
||||
@@ -353,13 +423,13 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
|
||||
// Execute Assembly
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.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(cmd),
|
||||
IsDLL: isDLL,
|
||||
Process: processName,
|
||||
Arguments: extArgs,
|
||||
Arguments: args,
|
||||
Assembly: binData,
|
||||
Arch: arch,
|
||||
Method: method,
|
||||
@@ -386,11 +456,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintAssemblyOutput(cmd.Name(), executeAssemblyResp, outFilePath, con)
|
||||
PrintAssemblyOutput(cmd.Name(), aliasManifest.Schema, executeAssemblyResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(executeAssemblyResp.Response)
|
||||
} else {
|
||||
PrintAssemblyOutput(cmd.Name(), executeAssemblyResp, outFilePath, con)
|
||||
PrintAssemblyOutput(cmd.Name(), aliasManifest.Schema, executeAssemblyResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else if aliasManifest.IsReflective {
|
||||
@@ -399,11 +469,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
|
||||
// Spawn DLL
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.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(cmd),
|
||||
Args: strings.Trim(extArgs, " "),
|
||||
Args: args,
|
||||
Data: binData,
|
||||
ProcessName: processName,
|
||||
EntryPoint: aliasManifest.Entrypoint,
|
||||
@@ -425,11 +495,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSpawnDLLOutput(cmd.Name(), spawnDllResp, outFilePath, con)
|
||||
PrintSpawnDLLOutput(cmd.Name(), aliasManifest.Schema, spawnDllResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(spawnDllResp.Response)
|
||||
} else {
|
||||
PrintSpawnDLLOutput(cmd.Name(), spawnDllResp, outFilePath, con)
|
||||
PrintSpawnDLLOutput(cmd.Name(), aliasManifest.Schema, spawnDllResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -438,11 +508,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
|
||||
// Sideload
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.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(cmd),
|
||||
Args: extArgs,
|
||||
Args: args,
|
||||
Data: binData,
|
||||
EntryPoint: entryPoint,
|
||||
ProcessName: processName,
|
||||
@@ -465,36 +535,80 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSideloadOutput(cmd.Name(), sideloadResp, outFilePath, con)
|
||||
PrintSideloadOutput(cmd.Name(), aliasManifest.Schema, sideloadResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(sideloadResp.Response)
|
||||
} else {
|
||||
PrintSideloadOutput(cmd.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())
|
||||
@@ -536,3 +650,105 @@ func makeAliasPlatformFilters(alias *AliasManifest) 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,15 +24,14 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// AliasesRemoveCmd - Locally load a alias into the Sliver shell.
|
||||
func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name := args[0]
|
||||
// name := ctx.Args.String("name")
|
||||
if name == "" {
|
||||
@@ -40,8 +39,7 @@ func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
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
|
||||
}
|
||||
@@ -54,8 +52,8 @@ func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
+383
-89
@@ -23,6 +23,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -54,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
|
||||
@@ -89,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(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
armoriesConfig := assets.GetArmoriesConfig()
|
||||
con.PrintInfof("Fetching %d armory index(es) ... ", len(armoriesConfig))
|
||||
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(armoriesConfig, clientConfig)
|
||||
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 {
|
||||
@@ -141,7 +180,7 @@ func ArmoryCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri
|
||||
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
|
||||
@@ -149,32 +188,52 @@ func ArmoryCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri
|
||||
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 {
|
||||
@@ -183,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
|
||||
@@ -191,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 {
|
||||
@@ -207,7 +385,7 @@ func AliasExtensionOrBundleCompleter() carapace.Action {
|
||||
var action carapace.Action
|
||||
|
||||
results := []string{}
|
||||
aliases, exts := packagesInCache()
|
||||
aliases, exts := packageManifestsInCache()
|
||||
bundles := bundlesInCache()
|
||||
|
||||
for _, aliasPkg := range aliases {
|
||||
@@ -217,9 +395,11 @@ func AliasExtensionOrBundleCompleter() carapace.Action {
|
||||
aliasesComps := carapace.ActionValuesDescribed(results...).Tag("aliases").Invoke(ctx)
|
||||
results = make([]string, 0)
|
||||
|
||||
for _, extensionPkg := range exts {
|
||||
results = append(results, extensionPkg.CommandName)
|
||||
results = append(results, extensionPkg.Help)
|
||||
for _, extension := range exts {
|
||||
for _, extensionPkg := range extension.ExtCommand {
|
||||
results = append(results, extensionPkg.CommandName)
|
||||
results = append(results, extensionPkg.Help)
|
||||
}
|
||||
}
|
||||
extentionComps := carapace.ActionValuesDescribed(results...).Tag("extensions").Invoke(ctx)
|
||||
results = make([]string, 0)
|
||||
@@ -240,7 +420,7 @@ func AliasExtensionOrBundleCompleter() carapace.Action {
|
||||
}
|
||||
|
||||
// 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 = 1
|
||||
@@ -254,6 +434,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
if con.Settings.SmallTermWidth+urlMargin < width {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory",
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
@@ -262,6 +443,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory",
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
@@ -271,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
|
||||
@@ -282,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",
|
||||
@@ -291,14 +476,17 @@ 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")
|
||||
@@ -311,6 +499,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
}
|
||||
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),
|
||||
@@ -319,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),
|
||||
@@ -331,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",
|
||||
})
|
||||
@@ -361,6 +552,7 @@ func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverConsoleClien
|
||||
}
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
bundle.ArmoryName,
|
||||
bundle.Name,
|
||||
packages,
|
||||
})
|
||||
@@ -398,12 +590,19 @@ func parseArmoryHTTPConfig(cmd *cobra.Command) ArmoryHTTPConfig {
|
||||
|
||||
// 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 {
|
||||
@@ -416,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,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)
|
||||
@@ -524,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}
|
||||
}
|
||||
+608
-156
@@ -19,120 +19,596 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
// 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(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
name := args[0]
|
||||
// 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(cmd)
|
||||
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
|
||||
@@ -159,20 +635,15 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
tmpFileName, err := writeArmoryTempFile(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
_, err = tmpFile.Write(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpFile.Close()
|
||||
defer os.Remove(tmpFileName)
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear the line
|
||||
|
||||
installPath := alias.InstallFromFile(tmpFile.Name(), true, con)
|
||||
installPath := alias.InstallFromFile(tmpFileName, entry.Alias.CommandName, promptToOverwrite, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install alias")
|
||||
}
|
||||
@@ -186,72 +657,44 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
|
||||
return nil
|
||||
}
|
||||
|
||||
func installExtension(ext *extensions.ExtensionManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
deps := make(map[string]struct{})
|
||||
resolveExtensionPackageDependencies(ext.CommandName, deps, clientConfig, con)
|
||||
sliverMenu := con.App.Menu(constants.ImplantMenu)
|
||||
for dep := range deps {
|
||||
if extensions.CmdExists(dep, sliverMenu.Command) {
|
||||
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")
|
||||
}
|
||||
@@ -281,36 +724,45 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
_, err = tmpFile.Write(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tmpFile.Sync()
|
||||
tmpFileName, err := writeArmoryTempFile(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFileName)
|
||||
|
||||
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
|
||||
}
|
||||
extensions.InstallFromDir(tmpFileName, promptToOverwrite, con, true)
|
||||
|
||||
sliverMenu := con.App.Menu(constants.ImplantMenu)
|
||||
//
|
||||
// if extensions.CmdExists(extCmd.Name, sliverMenu.Command) {
|
||||
// con.App.Commands().Remove(extCmd.Name)
|
||||
// }
|
||||
extensions.ExtensionRegisterCommand(extCmd, sliverMenu.Command, con)
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeArmoryTempFile(data []byte) (string, error) {
|
||||
if len(data) == 0 {
|
||||
return "", errors.New("downloaded archive is empty")
|
||||
}
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpFileName := tmpFile.Name()
|
||||
for len(data) > 0 {
|
||||
n, err := tmpFile.Write(data)
|
||||
if err != nil {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFileName)
|
||||
return "", err
|
||||
}
|
||||
data = data[n:]
|
||||
}
|
||||
if err := tmpFile.Sync(); err != nil {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFileName)
|
||||
return "", err
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
os.Remove(tmpFileName)
|
||||
return "", err
|
||||
}
|
||||
return tmpFileName, 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")
|
||||
}
|
||||
@@ -19,6 +19,8 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -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,7 +93,10 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, body, _ := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
@@ -111,9 +128,45 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
|
||||
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
|
||||
@@ -122,25 +175,29 @@ func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *Armory
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
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
|
||||
}
|
||||
@@ -242,6 +299,20 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
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
|
||||
}
|
||||
|
||||
@@ -436,6 +507,12 @@ func downloadRequest(clientConfig ArmoryHTTPConfig, reqURL string, armoryConfig
|
||||
"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)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
)
|
||||
|
||||
// ArmorySearchCmd - Search for packages by name
|
||||
func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintInfof("Refreshing package cache ... ")
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
refresh(clientConfig)
|
||||
@@ -47,7 +47,7 @@ func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
return
|
||||
}
|
||||
|
||||
aliases, exts := packagesInCache()
|
||||
aliases, exts := packageManifestsInCache()
|
||||
matchedAliases := []*alias.AliasManifest{}
|
||||
for _, a := range aliases {
|
||||
if nameExpr.MatchString(a.CommandName) {
|
||||
@@ -55,9 +55,11 @@ func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
}
|
||||
}
|
||||
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 {
|
||||
|
||||
+259
-37
@@ -19,56 +19,129 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"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/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(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func ArmoryUpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var selectedUpdates []UpdateIdentifier
|
||||
var err error
|
||||
|
||||
con.PrintInfof("Refreshing package cache ... ")
|
||||
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 := os.ReadFile(aliasManifestPath)
|
||||
if err != nil {
|
||||
@@ -79,19 +152,31 @@ 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 := os.ReadFile(extManifestPath)
|
||||
if err != nil {
|
||||
@@ -102,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.
|
||||
@@ -21,14 +21,13 @@ package backdoor
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// BackdoorCmd - Command to inject implant code into an existing binary
|
||||
func BackdoorCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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
|
||||
|
||||
@@ -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.
|
||||
@@ -36,9 +36,10 @@ import (
|
||||
)
|
||||
|
||||
// BeaconsCmd - Display/interact with beacons
|
||||
func BeaconsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
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 != "" {
|
||||
@@ -72,6 +73,18 @@ func BeaconsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []str
|
||||
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
|
||||
}
|
||||
}
|
||||
|
||||
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 filterRe, _ := cmd.Flags().GetString("filter-re"); filterRe != "" {
|
||||
@@ -94,7 +107,7 @@ func BeaconsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []str
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -103,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
|
||||
@@ -112,20 +125,45 @@ 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",
|
||||
@@ -145,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
|
||||
@@ -158,11 +199,19 @@ 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]),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -26,9 +26,8 @@ import (
|
||||
"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,7 +42,7 @@ var (
|
||||
)
|
||||
|
||||
// SelectBeacon - Interactive menu for the user to select an session, optionally only display live sessions
|
||||
func SelectBeacon(con *console.SliverConsoleClient) (*clientpb.Beacon, error) {
|
||||
func SelectBeacon(con *console.SliverClient) (*clientpb.Beacon, error) {
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
|
||||
@@ -83,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
|
||||
}
|
||||
@@ -102,7 +97,7 @@ func SelectBeacon(con *console.SliverConsoleClient) (*clientpb.Beacon, error) {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
|
||||
func GetBeacon(con *console.SliverConsoleClient, beaconID string) (*clientpb.Beacon, error) {
|
||||
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{})
|
||||
@@ -120,7 +115,7 @@ func GetBeacon(con *console.SliverConsoleClient, beaconID string) (*clientpb.Bea
|
||||
return nil, ErrBeaconNotFound
|
||||
}
|
||||
|
||||
func GetBeacons(con *console.SliverConsoleClient) (*clientpb.Beacons, error) {
|
||||
func GetBeacons(con *console.SliverClient) (*clientpb.Beacons, error) {
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
|
||||
|
||||
@@ -21,16 +21,16 @@ package beacons
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// BeaconsPruneCmd - Prune stale beacons automatically
|
||||
func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
duration, _ := cmd.Flags().GetString("duration")
|
||||
pruneDuration, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
@@ -71,8 +71,7 @@ func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
}
|
||||
con.Println()
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Prune these beacons?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Prune these beacons?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
// BeaconsRmCmd - Display/interact with beacons
|
||||
func BeaconsRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func BeaconsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
beacon, err := SelectBeacon(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
)
|
||||
|
||||
// BeaconsWatchCmd - Watch your beacons in real-ish time
|
||||
func BeaconsWatchCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
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.
|
||||
@@ -23,17 +23,16 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// BuildersCmd - List external builders
|
||||
func BuildersCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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)
|
||||
@@ -46,7 +45,7 @@ func BuildersCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []st
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -21,11 +21,15 @@ package c2profiles
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
@@ -34,12 +38,17 @@ import (
|
||||
"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.SliverConsoleClient, args []string) {
|
||||
func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
|
||||
if profileName == constants.DefaultC2Profile {
|
||||
@@ -49,7 +58,11 @@ func C2ProfileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []s
|
||||
return
|
||||
}
|
||||
if len(httpC2Profiles.Configs) != 1 {
|
||||
profileName = selectC2Profile(httpC2Profiles.Configs)
|
||||
profileName, err = selectC2Profile(httpC2Profiles.Configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +74,7 @@ func C2ProfileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []s
|
||||
PrintC2Profiles(profile, con)
|
||||
}
|
||||
|
||||
func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
protocols := []string{constants.HttpStr, constants.HttpsStr}
|
||||
func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
if profileName == "" {
|
||||
con.PrintErrorf("Invalid c2 profile name\n")
|
||||
@@ -98,31 +110,218 @@ func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, ar
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Restart HTTP/S jobs?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if confirm {
|
||||
var restartJobReq clientpb.RestartJobReq
|
||||
jobs, err := con.Rpc.GetJobs(context.Background(), &commonpb.Empty{})
|
||||
}
|
||||
|
||||
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("%s\n", err)
|
||||
con.PrintErrorf("failed to fetch HTTP C2 profiles: %s", err.Error())
|
||||
return
|
||||
}
|
||||
// reload jobs to include new profile
|
||||
for _, job := range jobs.Active {
|
||||
if job != nil && slices.Contains(protocols, job.Name) {
|
||||
restartJobReq.JobIDs = append(restartJobReq.JobIDs, job.ID)
|
||||
if len(httpC2Profiles.Configs) != 1 {
|
||||
profileName, err = selectC2Profile(httpC2Profiles.Configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = con.Rpc.RestartJobs(context.Background(), &restartJobReq)
|
||||
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 {
|
||||
|
||||
@@ -131,101 +330,66 @@ func C2ConfigToProtobuf(profileName string, config *assets.HTTPC2Config) *client
|
||||
pathSegments := []*clientpb.HTTPC2PathSegment{}
|
||||
|
||||
// files
|
||||
for _, poll := range config.ImplantConfig.PollFiles {
|
||||
for _, file := range config.ImplantConfig.Files {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 0,
|
||||
Value: poll,
|
||||
IsFile: true,
|
||||
Value: file,
|
||||
})
|
||||
}
|
||||
|
||||
for _, session := range config.ImplantConfig.SessionFiles {
|
||||
for _, path := range config.ImplantConfig.Paths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 1,
|
||||
Value: session,
|
||||
IsFile: false,
|
||||
Value: path,
|
||||
})
|
||||
}
|
||||
|
||||
for _, close := range config.ImplantConfig.CloseFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 2,
|
||||
Value: close,
|
||||
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 _, stager := range config.ImplantConfig.StagerFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 3,
|
||||
Value: stager,
|
||||
})
|
||||
}
|
||||
|
||||
// paths
|
||||
for _, poll := range config.ImplantConfig.PollPaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 0,
|
||||
Value: poll,
|
||||
})
|
||||
}
|
||||
|
||||
for _, session := range config.ImplantConfig.SessionPaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 1,
|
||||
Value: session,
|
||||
})
|
||||
}
|
||||
|
||||
for _, close := range config.ImplantConfig.ClosePaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 2,
|
||||
Value: close,
|
||||
})
|
||||
}
|
||||
|
||||
for _, stager := range config.ImplantConfig.StagerPaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 3,
|
||||
Value: stager,
|
||||
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,
|
||||
ExtraURLParameters: httpC2UrlParameters,
|
||||
Headers: httpC2Headers,
|
||||
MaxFiles: int32(config.ImplantConfig.MaxFiles),
|
||||
MinFiles: int32(config.ImplantConfig.MinFiles),
|
||||
MaxPaths: int32(config.ImplantConfig.MaxPaths),
|
||||
MinPaths: int32(config.ImplantConfig.MinFiles),
|
||||
StagerFileExtension: config.ImplantConfig.StagerFileExt,
|
||||
PollFileExtension: config.ImplantConfig.PollFileExt,
|
||||
StartSessionFileExtension: config.ImplantConfig.StartSessionFileExt,
|
||||
SessionFileExtension: config.ImplantConfig.SessionFileExt,
|
||||
CloseFileExtension: config.ImplantConfig.CloseFileExt,
|
||||
PathSegments: pathSegments,
|
||||
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 {
|
||||
for _, method := range serverHeader.Methods {
|
||||
serverHeaders = append(serverHeaders, &clientpb.HTTPC2Header{
|
||||
Method: method,
|
||||
Name: serverHeader.Name,
|
||||
Value: serverHeader.Value,
|
||||
Probability: int32(serverHeader.Probability),
|
||||
})
|
||||
}
|
||||
serverHeaders = append(serverHeaders, &clientpb.HTTPC2Header{
|
||||
Method: serverHeader.Method,
|
||||
Name: serverHeader.Name,
|
||||
Value: serverHeader.Value,
|
||||
Probability: int32(serverHeader.Probability),
|
||||
})
|
||||
}
|
||||
|
||||
serverCookies := []*clientpb.HTTPC2Cookie{}
|
||||
@@ -248,7 +412,7 @@ func C2ConfigToProtobuf(profileName string, config *assets.HTTPC2Config) *client
|
||||
}
|
||||
|
||||
// PrintImplantBuilds - Print the implant builds on the server
|
||||
func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverConsoleClient) {
|
||||
func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient) {
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
@@ -268,7 +432,7 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverConsoleC
|
||||
|
||||
var serverHeaders []string
|
||||
for _, header := range profile.ServerConfig.Headers {
|
||||
serverHeaders = append(serverHeaders, header.Value)
|
||||
serverHeaders = append(serverHeaders, header.Name)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Server Headers",
|
||||
@@ -293,7 +457,7 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverConsoleC
|
||||
|
||||
var clientHeaders []string
|
||||
for _, header := range profile.ImplantConfig.Headers {
|
||||
clientHeaders = append(clientHeaders, header.Value)
|
||||
clientHeaders = append(clientHeaders, header.Name)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Client Headers",
|
||||
@@ -324,114 +488,139 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverConsoleC
|
||||
"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.MaxFiles,
|
||||
profile.ImplantConfig.MaxFileGen,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Min files",
|
||||
profile.ImplantConfig.MinFiles,
|
||||
profile.ImplantConfig.MinFileGen,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Max paths",
|
||||
profile.ImplantConfig.MaxPaths,
|
||||
profile.ImplantConfig.MaxPathGen,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Min paths",
|
||||
profile.ImplantConfig.MinPaths,
|
||||
profile.ImplantConfig.MinPathGen,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Stager file extension",
|
||||
profile.ImplantConfig.StagerFileExtension,
|
||||
"Max path length",
|
||||
profile.ImplantConfig.MaxPathLength,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Start session file extension",
|
||||
profile.ImplantConfig.StartSessionFileExtension,
|
||||
"Min path length",
|
||||
profile.ImplantConfig.MinPathLength,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Session file extension",
|
||||
profile.ImplantConfig.SessionFileExtension,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Poll file extension",
|
||||
profile.ImplantConfig.PollFileExtension,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Close file extension",
|
||||
profile.ImplantConfig.CloseFileExtension,
|
||||
"File extensions",
|
||||
strings.Join(profile.ImplantConfig.Extensions, ","),
|
||||
})
|
||||
|
||||
var (
|
||||
pollPaths []string
|
||||
pollFiles []string
|
||||
sessionPaths []string
|
||||
sessionFiles []string
|
||||
closePaths []string
|
||||
closeFiles []string
|
||||
paths []string
|
||||
files []string
|
||||
)
|
||||
for _, segment := range profile.ImplantConfig.PathSegments {
|
||||
if segment.IsFile {
|
||||
switch segment.SegmentType {
|
||||
case 0:
|
||||
pollFiles = append(pollFiles, segment.Value)
|
||||
case 1:
|
||||
sessionFiles = append(sessionFiles, segment.Value)
|
||||
case 2:
|
||||
closeFiles = append(closeFiles, segment.Value)
|
||||
}
|
||||
files = append(files, segment.Value)
|
||||
} else {
|
||||
switch segment.SegmentType {
|
||||
case 0:
|
||||
pollPaths = append(pollPaths, segment.Value)
|
||||
case 1:
|
||||
sessionPaths = append(sessionPaths, segment.Value)
|
||||
case 2:
|
||||
closePaths = append(closePaths, segment.Value)
|
||||
}
|
||||
paths = append(paths, segment.Value)
|
||||
}
|
||||
}
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Poll paths",
|
||||
strings.Join(pollPaths[:], ","),
|
||||
"Paths",
|
||||
strings.Join(paths[:], ",")[:50] + "...",
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Poll files",
|
||||
strings.Join(pollFiles[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Session paths",
|
||||
strings.Join(sessionPaths[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Session files",
|
||||
strings.Join(sessionFiles[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Close paths",
|
||||
strings.Join(closePaths[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Close files",
|
||||
strings.Join(closeFiles[:], ","),
|
||||
"Files",
|
||||
strings.Join(files[:], ",")[:50] + "...",
|
||||
})
|
||||
|
||||
con.Println(tw.Render())
|
||||
con.Println("\n")
|
||||
}
|
||||
|
||||
func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
|
||||
func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) (string, error) {
|
||||
c2profile := ""
|
||||
var choices []string
|
||||
for _, c2profile := range c2profiles {
|
||||
choices = append(choices, c2profile.Name)
|
||||
}
|
||||
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a c2 profile",
|
||||
Options: choices,
|
||||
}
|
||||
survey.AskOne(prompt, &c2profile, nil)
|
||||
_ = forms.Select("Select a c2 profile", choices, &c2profile)
|
||||
|
||||
return 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,12 @@
|
||||
# client/command/certificates
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'certificates' command group for the Sliver client console.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `authorities.go` – Retrieves certificate authority metadata from the server and prints it in a table.
|
||||
- `completions.go` – Provides tab completion helpers for certificate common names.
|
||||
- `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,90 @@
|
||||
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/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func CertificateAuthoritiesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
authorityInfo, err := con.Rpc.GetCertificateAuthorityInfo(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not get certificate authority information from database: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
printCertificateAuthorityInfo(con, authorityInfo.Info)
|
||||
}
|
||||
|
||||
func printCertificateAuthorityInfo(con *console.SliverClient, authData []*clientpb.CertificateAuthorityData) {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
if len(authData) == 0 {
|
||||
con.PrintWarnf("There are no certificate authorities in the database.\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",
|
||||
"Authority Type",
|
||||
"Key Algorithm",
|
||||
"Validity Start",
|
||||
"Expires",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Common Name",
|
||||
"Expires",
|
||||
})
|
||||
}
|
||||
|
||||
for _, authority := range authData {
|
||||
rowColor := console.Normal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
|
||||
if err == nil {
|
||||
rowColor = checkCertExpiry(expiry)
|
||||
}
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CreationTime),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.Type),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.KeyAlgorithm),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityStart),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -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,65 @@
|
||||
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/rsteube/carapace"
|
||||
"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")
|
||||
})
|
||||
flags.BindFlagCompletions(certificatesCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["cn"] = CertificateCommonNameCompleter(con)
|
||||
})
|
||||
registerCertificateCNFlagCompletion(certificatesCmd, "cn", con)
|
||||
|
||||
authoritiesCmd := &cobra.Command{
|
||||
Use: "authorities",
|
||||
Short: "List certificate authorities",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CertificateAuthoritiesCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
certificatesCmd.AddCommand(authoritiesCmd)
|
||||
|
||||
return []*cobra.Command{
|
||||
certificatesCmd,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CertificateCommonNameCompleter completes common names for stored certificates.
|
||||
func CertificateCommonNameCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return carapace.ActionMessage("not connected")
|
||||
}
|
||||
|
||||
values, err := certificateCommonNameValues(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to list certificates %s", err)
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return carapace.ActionMessage("no certificates")
|
||||
}
|
||||
|
||||
return carapace.ActionValues(values...).Tag("common name").Usage("certificate common name")
|
||||
})
|
||||
}
|
||||
|
||||
func registerCertificateCNFlagCompletion(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, err := certificateCommonNameValues(con)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func certificateCommonNameValues(con *console.SliverClient) ([]string, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
certInfo, err := con.Rpc.GetCertificateInfo(context.Background(), &clientpb.CertificatesReq{
|
||||
CategoryFilters: AllTransports | AllRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unique := map[string]struct{}{}
|
||||
for _, cert := range certInfo.Info {
|
||||
if cert.CN == "" {
|
||||
continue
|
||||
}
|
||||
unique[cert.CN] = struct{}{}
|
||||
}
|
||||
|
||||
values := make([]string, 0, len(unique))
|
||||
for name := range unique {
|
||||
values = append(values, name)
|
||||
}
|
||||
sort.Strings(values)
|
||||
return values, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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,
|
||||
}}
|
||||
}
|
||||
+69
-12
@@ -21,19 +21,18 @@ package command
|
||||
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"
|
||||
)
|
||||
|
||||
const defaultTimeout = 60
|
||||
|
||||
// Flags is a convenience function to bind flags to a given command.
|
||||
// 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 Flags(name string, persistent bool, cmd *cobra.Command, flags func(f *pflag.FlagSet)) {
|
||||
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.
|
||||
|
||||
@@ -44,22 +43,21 @@ func Flags(name string, persistent bool, cmd *cobra.Command, flags func(f *pflag
|
||||
}
|
||||
}
|
||||
|
||||
// FlagComps is a convenience function for adding completions to a command's flags.
|
||||
// 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 FlagComps(cmd *cobra.Command, bind func(comp *carapace.ActionMap)) {
|
||||
func BindFlagCompletions(cmd *cobra.Command, bind func(comp *carapace.ActionMap)) {
|
||||
comps := make(carapace.ActionMap)
|
||||
bind(&comps)
|
||||
|
||||
carapace.Gen(cmd).FlagCompletion(comps)
|
||||
}
|
||||
|
||||
// hideCommand generates a cobra annotation map with a single
|
||||
// console.CommandHiddenFilter key, which value is a comma-separated list
|
||||
// of filters to use in order to expose/hide commands based on requirements.
|
||||
// Ex: cmd.Annotations = hideCommand("windows") will hide the cmd
|
||||
// if the target session/beacon is not a Windows host.
|
||||
func hideCommand(filters ...string) map[string]string {
|
||||
// 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
|
||||
}
|
||||
@@ -76,3 +74,62 @@ func hideCommand(filters ...string) 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.
|
||||
@@ -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.
|
||||
@@ -55,3 +55,32 @@ func ClientInterfacesCompleter() carapace.Action {
|
||||
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}
|
||||
}
|
||||
@@ -25,18 +25,17 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"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/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.SliverConsoleClient, args []string) {
|
||||
// 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)
|
||||
@@ -55,8 +54,8 @@ func CrackWordlistsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, arg
|
||||
}
|
||||
}
|
||||
|
||||
// CrackRulesCmd - Manage GPU cracking stations
|
||||
func CrackRulesCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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)
|
||||
@@ -75,8 +74,8 @@ func CrackRulesCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []
|
||||
}
|
||||
}
|
||||
|
||||
// CrackHcstat2Cmd - Manage GPU cracking stations
|
||||
func CrackHcstat2Cmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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)
|
||||
@@ -95,7 +94,7 @@ func CrackHcstat2Cmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
}
|
||||
}
|
||||
|
||||
func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverConsoleClient) {
|
||||
func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"Name", "Size"})
|
||||
@@ -105,7 +104,7 @@ func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverConsole
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverConsoleClient) {
|
||||
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
|
||||
wordlistTable := table.NewWriter()
|
||||
wordlistTable.SetTitle(console.Bold + "Wordlists" + console.Normal)
|
||||
wordlistTable.SetStyle(settings.GetTableStyle(con))
|
||||
@@ -162,8 +161,8 @@ func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverC
|
||||
)
|
||||
}
|
||||
|
||||
// CrackWordlistsAddCmd - Manage GPU cracking stations
|
||||
func CrackWordlistsAddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// CrackWordlistsAddCmd - Manage GPU cracking stations.
|
||||
func CrackWordlistsAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
var localPath string
|
||||
@@ -206,8 +205,8 @@ func CrackWordlistsAddCmd(cmd *cobra.Command, con *console.SliverConsoleClient,
|
||||
addCrackFile(wordlist, crackFile, con)
|
||||
}
|
||||
|
||||
// CrackRulesAddCmd - add a rules file
|
||||
func CrackRulesAddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// CrackRulesAddCmd - add a rules file.
|
||||
func CrackRulesAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
var localPath string
|
||||
@@ -250,8 +249,8 @@ func CrackRulesAddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
addCrackFile(rules, crackFile, con)
|
||||
}
|
||||
|
||||
// CrackHcstat2AddCmd - add a hcstat2 file
|
||||
func CrackHcstat2AddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// CrackHcstat2AddCmd - add a hcstat2 file.
|
||||
func CrackHcstat2AddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
var localPath string
|
||||
@@ -294,7 +293,7 @@ func CrackHcstat2AddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, ar
|
||||
addCrackFile(hcstat2, crackFile, con)
|
||||
}
|
||||
|
||||
func addCrackFile(localFile *os.File, crackFile *clientpb.CrackFile, con *console.SliverConsoleClient) {
|
||||
func addCrackFile(localFile *os.File, crackFile *clientpb.CrackFile, con *console.SliverClient) {
|
||||
digest := sha256.New()
|
||||
wordlistReader := io.TeeReader(localFile, digest)
|
||||
|
||||
@@ -402,8 +401,8 @@ func readChunkAt(tmpFile *os.File, offset int64, chunkSize int64) ([]byte, error
|
||||
return chunkBuf[:n], nil
|
||||
}
|
||||
|
||||
// CrackWordlistsRmCmd - Manage GPU cracking stations
|
||||
func CrackWordlistsRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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]
|
||||
@@ -436,8 +435,8 @@ func CrackWordlistsRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, a
|
||||
}
|
||||
}
|
||||
|
||||
// CrackRulesRmCmd - Manage GPU cracking stations
|
||||
func CrackRulesRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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]
|
||||
@@ -470,8 +469,8 @@ func CrackRulesRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
}
|
||||
}
|
||||
|
||||
// CrackHcstat2RmCmd - remove a hcstat2 file
|
||||
func CrackHcstat2RmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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]
|
||||
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
)
|
||||
|
||||
// CrackCmd - GPU password cracking interface
|
||||
func CrackCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if !AreCrackersOnline(con) {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
@@ -58,7 +58,7 @@ func CrackCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []strin
|
||||
}
|
||||
|
||||
// CrackStationsCmd - Manage GPU cracking stations
|
||||
func CrackStationsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
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)
|
||||
@@ -71,11 +71,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
}
|
||||
}
|
||||
|
||||
func PrintNoCrackstations(con *console.SliverConsoleClient) {
|
||||
func PrintNoCrackstations(con *console.SliverClient) {
|
||||
con.PrintInfof("No crackstations connected to server\n")
|
||||
}
|
||||
|
||||
func AreCrackersOnline(con *console.SliverConsoleClient) bool {
|
||||
func AreCrackersOnline(con *console.SliverClient) bool {
|
||||
crackers, err := con.Rpc.Crackstations(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -83,7 +83,7 @@ func AreCrackersOnline(con *console.SliverConsoleClient) bool {
|
||||
return len(crackers.Crackstations) > 0
|
||||
}
|
||||
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverConsoleClient) {
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
|
||||
sort.Slice(crackers, func(i, j int) bool {
|
||||
return crackers[i].Name < crackers[j].Name
|
||||
})
|
||||
@@ -96,7 +96,7 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverConsole
|
||||
}
|
||||
}
|
||||
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverConsoleClient) {
|
||||
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")
|
||||
@@ -135,7 +135,7 @@ func printCracker(cracker *clientpb.Crackstation, index int, con *console.Sliver
|
||||
printBenchmarks(cracker, con)
|
||||
}
|
||||
|
||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverConsoleClient) {
|
||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/rsteube/carapace"
|
||||
)
|
||||
|
||||
func CrackHcstat2Completer(con *console.SliverConsoleClient) carapace.Action {
|
||||
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 {
|
||||
@@ -33,7 +33,7 @@ func CrackHcstat2Completer(con *console.SliverConsoleClient) carapace.Action {
|
||||
})
|
||||
}
|
||||
|
||||
func CrackWordlistCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
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 {
|
||||
@@ -57,7 +57,7 @@ func CrackWordlistCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
})
|
||||
}
|
||||
|
||||
func CrackRulesCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
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 {
|
||||
|
||||
@@ -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.
|
||||
@@ -37,8 +37,8 @@ const (
|
||||
CSVFormat = "csv" // username,hash\n
|
||||
)
|
||||
|
||||
// CredsCmd - Add new credentials
|
||||
func CredsAddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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")
|
||||
@@ -76,8 +76,8 @@ func CredsAddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []st
|
||||
PrintCreds(creds.Credentials, con)
|
||||
}
|
||||
|
||||
// CredsCmd - Add new credentials
|
||||
func CredsAddHashFileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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")
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
@@ -23,18 +23,17 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/rsteube/carapace"
|
||||
"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"
|
||||
"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.SliverConsoleClient, args []string) {
|
||||
// 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)
|
||||
@@ -47,7 +46,7 @@ func CredsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []strin
|
||||
PrintCreds(creds.Credentials, con)
|
||||
}
|
||||
|
||||
func PrintCreds(creds []*clientpb.Credential, con *console.SliverConsoleClient) {
|
||||
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)
|
||||
@@ -58,7 +57,7 @@ func PrintCreds(creds []*clientpb.Credential, con *console.SliverConsoleClient)
|
||||
}
|
||||
}
|
||||
|
||||
func printCollection(collection string, creds []*clientpb.Credential, con *console.SliverConsoleClient) {
|
||||
func printCollection(collection string, creds []*clientpb.Credential, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
if collection != "" {
|
||||
@@ -88,7 +87,7 @@ func printCollection(collection string, creds []*clientpb.Credential, con *conso
|
||||
}
|
||||
|
||||
// CredsHashTypeCompleter completes hash types.
|
||||
func CredsHashTypeCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
func CredsHashTypeCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
@@ -101,8 +100,8 @@ func CredsHashTypeCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
})
|
||||
}
|
||||
|
||||
// CredsHashFileFormatCompleter completes file formats for hash-files
|
||||
func CredsHashFileFormatCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
// 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.",
|
||||
@@ -110,8 +109,8 @@ func CredsHashFileFormatCompleter(con *console.SliverConsoleClient) carapace.Act
|
||||
).Tag("hash file formats")
|
||||
}
|
||||
|
||||
// CredsCollectionCompleter completes existing creds collection names
|
||||
func CredsCollectionCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
// 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)
|
||||
|
||||
@@ -134,7 +133,7 @@ func CredsCollectionCompleter(con *console.SliverConsoleClient) carapace.Action
|
||||
}
|
||||
|
||||
// CredsCredentialIDCompleter completes credential IDs.
|
||||
func CredsCredentialIDCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
func CredsCredentialIDCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CredsCmd - Add new credentials
|
||||
func CredsRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// CredsCmd - Add new credentials.
|
||||
func CredsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var id string
|
||||
if len(args) > 0 {
|
||||
id = args[0]
|
||||
|
||||
@@ -8,15 +8,14 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// SelectCredential - Interactive menu for the user to select a credentials from the database
|
||||
func SelectCredential(plaintext bool, hashType clientpb.HashType, con *console.SliverConsoleClient) (*clientpb.Credential, error) {
|
||||
// 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 {
|
||||
@@ -56,11 +55,7 @@ func SelectCredential(plaintext bool, hashType clientpb.HashType, con *console.S
|
||||
}
|
||||
|
||||
selected := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a credential:",
|
||||
Options: options,
|
||||
}
|
||||
err = survey.AskOne(prompt, &selected)
|
||||
err = forms.Select("Select a credential:", options, &selected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
@@ -23,21 +23,20 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -51,8 +50,8 @@ var (
|
||||
cursedChromePermissionsAlt = []string{overlord.AllHTTP, overlord.AllHTTPS, overlord.WebRequest, overlord.WebRequestBlocking}
|
||||
)
|
||||
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory
|
||||
func CursedChromeCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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
|
||||
@@ -112,7 +111,7 @@ func CursedChromeCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
}
|
||||
}
|
||||
|
||||
func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) *core.CursedProcess {
|
||||
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)
|
||||
@@ -124,7 +123,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, 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 Chrome process?"}, &confirm)
|
||||
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -154,7 +153,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
|
||||
return curse
|
||||
}
|
||||
|
||||
func startCursedChromeProcess(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) (*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"
|
||||
@@ -214,7 +213,7 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, cmd *cobra
|
||||
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)
|
||||
@@ -254,7 +253,7 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, cmd *cobra
|
||||
return curse, nil
|
||||
}
|
||||
|
||||
func findChromeUserDataDir(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient) (string, error) {
|
||||
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
|
||||
@@ -307,7 +306,7 @@ func findChromeUserDataDir(isEdge bool, session *clientpb.Session, cmd *cobra.Co
|
||||
}
|
||||
}
|
||||
|
||||
func findChromeExecutablePath(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient) (string, error) {
|
||||
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
|
||||
@@ -417,7 +416,7 @@ func isChromeProcess(executable string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getChromeProcess(session *clientpb.Session, cmd *cobra.Command, 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(cmd),
|
||||
})
|
||||
|
||||
@@ -26,16 +26,15 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/reeflective/readline"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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/reeflective/readline"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedConsoleCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func CursedConsoleCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
@@ -56,7 +55,7 @@ func CursedConsoleCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
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
|
||||
@@ -72,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
|
||||
@@ -94,7 +89,7 @@ var helperHooks = []string{
|
||||
"console.log = (...a) => {return a;}", // console.log
|
||||
}
|
||||
|
||||
func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlord.ChromeDebugTarget, con *console.SliverConsoleClient) {
|
||||
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())
|
||||
|
||||
@@ -21,16 +21,16 @@ package cursed
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
@@ -47,24 +47,24 @@ func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
if len(cookies) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
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 = os.WriteFile(saveFile, []byte(strings.Join(jsonCookies, "\n")), 0o600)
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -21,22 +21,20 @@ package cursed
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory
|
||||
func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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
|
||||
@@ -96,7 +94,7 @@ func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []
|
||||
}
|
||||
}
|
||||
|
||||
func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) *core.CursedProcess {
|
||||
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)
|
||||
@@ -108,7 +106,7 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
|
||||
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
|
||||
@@ -153,7 +151,7 @@ func isEdgeProcess(executable string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getEdgeProcess(session *clientpb.Session, cmd *cobra.Command, 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(cmd),
|
||||
})
|
||||
|
||||
@@ -22,23 +22,22 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
insecureRand "math/rand"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedElectronCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func CursedElectronCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
@@ -68,7 +67,7 @@ func CursedElectronCmd(cmd *cobra.Command, con *console.SliverConsoleClient, arg
|
||||
con.PrintInfof("Found %d debug targets, good hunting!\n", len(targets))
|
||||
}
|
||||
|
||||
func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) *core.CursedProcess {
|
||||
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)
|
||||
@@ -89,7 +88,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
|
||||
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
|
||||
@@ -119,7 +118,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
|
||||
return curse
|
||||
}
|
||||
|
||||
func checkElectronPath(electronExe string, session *clientpb.Session, cmd *cobra.Command, 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(cmd),
|
||||
Path: electronExe,
|
||||
@@ -130,7 +129,7 @@ func checkElectronPath(electronExe string, session *clientpb.Session, cmd *cobra
|
||||
return ls.GetExists(), nil
|
||||
}
|
||||
|
||||
func checkElectronProcess(electronExe string, session *clientpb.Session, cmd *cobra.Command, 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(cmd),
|
||||
})
|
||||
@@ -148,7 +147,7 @@ func checkElectronProcess(electronExe string, session *clientpb.Session, cmd *co
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func startCursedElectronProcess(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) (*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(cmd)
|
||||
args := []string{
|
||||
@@ -176,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)
|
||||
|
||||
@@ -22,15 +22,14 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func CursedRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
@@ -45,7 +44,7 @@ func CursedRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []st
|
||||
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
|
||||
|
||||
@@ -23,13 +23,12 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CursedScreenshotCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func CursedScreenshotCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
|
||||
@@ -21,22 +21,21 @@ package cursed
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"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/client/core"
|
||||
"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(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
// 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 {
|
||||
@@ -71,8 +70,8 @@ func CursedCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
@@ -96,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
|
||||
@@ -116,7 +111,7 @@ func selectCursedProcess(con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
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}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
// dllhijack --ref-path c:\windows\system32\msasn1.dll --ref-file /tmp/ref.dll --profile dll TARGET_PATH
|
||||
|
||||
// DllHijackCmd -- implements the dllhijack command
|
||||
func DllHijackCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
func DllHijackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var (
|
||||
localRefData []byte
|
||||
targetDLLData []byte
|
||||
|
||||
Loaded 100 of 11421 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user