├── .gitattributes ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── goqrexfil.go ├── payload └── payload.bin └── public ├── index.html └── render.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '28 15 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | goqrexfil 18 | *.png 19 | *.jpg 20 | qr 21 | *.mp4 22 | file.output.pdf 23 | .DS_Store 24 | payload.raw 25 | file.output 26 | top.secret.file 27 | *.png_original 28 | payload/payload.bin 29 | *.mov 30 | .vscode/launch.json 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 JMA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goqrexfil 2 | 3 | A mini project to exfiltrate data via QR codes - I just like writing code around data exfiltration. 4 | 5 | The whole idea in this one is that the data can be exfiltrated via a different cover channel using video recording 6 | device and therefore will not trigger any classic network monitoring alerts. 7 | 8 | In a first phase using --client, the code allows to take a file from stdin, cut it in pieces and display chunks as QR 9 | codes one at a time in your terminal. This allows you to create a video on your phone, just ensure that you are zooming 10 | and have the whole terminal in your focus. 11 | 12 | In a second phase using --server, you can use the same code elsewhere on a system you control and run a web server that 13 | will allow you to upload your video for processing. We rely on ffmpeg to extract frames from the recording and then a 14 | library to extract QR codes from the frames (minus potential duplicates). Finally, the payload is rebuilt from being 15 | retrieved by reading the data from the QR codes and a file is created with the original data. 16 | 17 | # Caveats/TODO 18 | 19 | * Currently very Alpha - works well with small text files, almost ok for PDFs/binary format (I need to have more time to 20 | debug this) 21 | * Ugly code - I am not a developper, I normally use Python and wanted to try to learn Golang (be nice if you want to 22 | help with improving this ugly code) 23 | 24 | # Example 25 | 26 | ## Part 1: Convert file into QR codes and video record 27 | 28 | 1. use goqrexfil in client mode 29 | 2. start a video recording with phone, point at the shell window 30 | 31 | Example: 32 | 33 | ``` 34 | ➜ cat top.secret.file | ./goqrexfil --client 35 | -= goqrexfil =- 36 | [*] Client mode: ON 37 | [*] Payload is in 8 chunks, video recording time estimate: 38 | 39 | 40 | ---=== 5 seconds to use CTRL+C if you want to abort ===--- 41 | ``` 42 | 43 | Start recording a video now, QR codes will be displayed on the console and stop the video at the end. 44 | 45 | ## Have server ready to receive and process your video 46 | 47 | 1. use goqrexfil in server mode 48 | 2. From your phone, go to your server domain/ip on port 8888 e.g. http://1.2.3.4:8888/ and upload the video: 49 | 50 | Example: 51 | 52 | ``` 53 | ➜ ./goqrexfil -server 54 | -= goqrexfil =- 55 | [*] Server mode: ON 56 | 2020/04/25 11:42:15 57 | [*] File received 58 | [*] Frames extracted 59 | 60 | public/004.png has payload.. Adding 61 | public/005.png has payload.. Adding 62 | 63 | [*] Payload retrieved (Wrote 824 bytes): payload.raw. 64 | [GIN] 2020/04/25 - 11:42:19 | 200 | 4.336634181s | 172.16.0.110 | POST "/upload" 65 | 66 | ^C⏎ 67 | ``` 68 | 69 | ## Retrieving payload 70 | 71 | ```➜ cat payload.raw 72 | ------------| 73 | 74 | 75 | ----| Intro 76 | 77 | Writing shellcode for the MIPS/Irix platform is not much different from writing 78 | shellcode for the x86 architecture. There are, however, a few tricks worth 79 | knowing when attempting to write clean shellcode (which does not have any NULL 80 | bytes and works completely independent from it's position). 81 | 82 | This small paper will provide you with a crash course on writing IRIX 83 | shellcode for use in exploits. It covers the basic stuff you need to know to 84 | start writing basic IRIX shellcode. It is divided into the following sections: 85 | 86 | - The IRIX operating system 87 | - MIPS archstages the MIPS design 88 | has reflected this on the instructions itself: every instruction is 89 | 32 bits broad (4 bytes), and can be divided most of the times into 90 | segments which correspond with each pipestage..``` 91 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module goqrexfil 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/boombuler/barcode v1.0.1 7 | github.com/gin-gonic/gin v1.9.1 8 | github.com/kjk/smaz v0.0.0-20151202183815-c61c680e82ff 9 | github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea 10 | github.com/sirupsen/logrus v1.8.1 11 | github.com/zserge/lorca v0.1.10 12 | golang.org/x/crypto v0.35.0 13 | ) 14 | 15 | require ( 16 | github.com/bytedance/sonic v1.9.1 // indirect 17 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 18 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 19 | github.com/gin-contrib/sse v0.1.0 // indirect 20 | github.com/go-playground/locales v0.14.1 // indirect 21 | github.com/go-playground/universal-translator v0.18.1 // indirect 22 | github.com/go-playground/validator/v10 v10.14.0 // indirect 23 | github.com/goccy/go-json v0.10.2 // indirect 24 | github.com/json-iterator/go v1.1.12 // indirect 25 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 26 | github.com/leodido/go-urn v1.2.4 // indirect 27 | github.com/mattn/go-isatty v0.0.19 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 31 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 32 | github.com/ugorji/go/codec v1.2.11 // indirect 33 | golang.org/x/arch v0.3.0 // indirect 34 | golang.org/x/net v0.23.0 // indirect 35 | golang.org/x/sys v0.30.0 // indirect 36 | golang.org/x/text v0.22.0 // indirect 37 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 38 | google.golang.org/protobuf v1.33.0 // indirect 39 | gopkg.in/yaml.v3 v3.0.1 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= 2 | github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 3 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 4 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 5 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 6 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 7 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 8 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 13 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 14 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 15 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 16 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 17 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 19 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 20 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 21 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 22 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 23 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 24 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 25 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 26 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 27 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 28 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 29 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 30 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 31 | github.com/kjk/smaz v0.0.0-20151202183815-c61c680e82ff h1:psAc8+Dcef8NTLSARr6v2vRWY0dkRcBI3BMypQazS4o= 32 | github.com/kjk/smaz v0.0.0-20151202183815-c61c680e82ff/go.mod h1:HZixo9PxzzUDLNc9n6lganlt0L6gfDaidFDzFw2dmXA= 33 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 34 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 35 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 36 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 37 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 38 | github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea h1:uyJ13zfy6l79CM3HnVhDalIyZ4RJAyVfDrbnfFeJoC4= 39 | github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea/go.mod h1:w4pGU9PkiX2hAWyF0yuHEHmYTQFAd6WHzp6+IY7JVjE= 40 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 41 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 42 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 45 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 46 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 47 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 48 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 49 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 50 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 52 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 53 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 54 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 55 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 56 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 57 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 58 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 59 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 60 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 61 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 62 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 63 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 64 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 65 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 66 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 67 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 68 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 69 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 70 | github.com/zserge/lorca v0.1.10 h1:f/xBJ3D3ipcVRCcvN8XqZnpoKcOXV8I4vwqlFyw7ruc= 71 | github.com/zserge/lorca v0.1.10/go.mod h1:bVmnIbIRlOcoV285KIRSe4bUABKi7R7384Ycuum6e4A= 72 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 73 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 74 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 75 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 76 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 77 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 78 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 79 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 80 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 81 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 82 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 83 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 84 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 85 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 86 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 87 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 88 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 89 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 90 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 91 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 92 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 93 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 94 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 95 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 96 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 97 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 98 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 99 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 100 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 101 | -------------------------------------------------------------------------------- /goqrexfil.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "flag" 8 | "fmt" 9 | "image" 10 | "image/jpeg" 11 | "image/png" 12 | "io" 13 | "io/ioutil" 14 | "net/http" 15 | "net/url" 16 | "os" 17 | "os/exec" 18 | "path/filepath" 19 | "strings" 20 | "time" 21 | 22 | "github.com/kjk/smaz" 23 | 24 | "github.com/boombuler/barcode" 25 | "github.com/boombuler/barcode/qr" 26 | "github.com/gin-gonic/gin" 27 | goqr "github.com/liyue201/goqr" 28 | log "github.com/sirupsen/logrus" 29 | "github.com/zserge/lorca" 30 | "golang.org/x/crypto/blake2b" 31 | ) 32 | 33 | const ( 34 | serverPort = "9999" // TCP port to run the web service on 35 | videoLocation = "./public/video.mp4" // location of video uploaded to the web service 36 | ffmpeg = "/opt/homebrew/bin/ffmpeg" // Path to local FFMPEG - required only for server mode 37 | ffmpegQuality = "16" // Qualify for frames to images conversion. 1-31. 5 for great, 10 for acceptable (this helps reduce file size) 38 | ffmpegImageScale = "scale=500:-1" // Scale for frames to images conversion. 500px + keep aspect ratio 39 | QRCDataMaxBytes = 320 // 230 was safe. If this gets too big, QR code will be hard to read... 40 | secsBeforeDisplay = 3 // 3 seconds before starting to display QR codes 41 | msBetweenFrames = 550 // milleseconds between QR codes displayed to allow proper recording 42 | retrieved = "./payload/payload.bin" // path to output payload file when video and all qr code data is retrieved 43 | ) 44 | 45 | func init() { 46 | var clear map[string]func() 47 | clear = make(map[string]func()) //Initialize it 48 | clear["linux"] = func() { 49 | cmd := exec.Command("clear") //Linux example, its tested 50 | cmd.Stdout = os.Stdout 51 | err := cmd.Run() 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | } 56 | clear["windows"] = func() { 57 | cmd := exec.Command("cmd", "/c", "cls") //Windows example, its tested 58 | cmd.Stdout = os.Stdout 59 | err := cmd.Run() 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | } 64 | clear["darwin"] = func() { 65 | cmd := exec.Command("clear") //Linux example, its tested 66 | cmd.Stdout = os.Stdout 67 | err := cmd.Run() 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | } 72 | } 73 | 74 | // RenderQR returns a QR code HTML image with encoded chunk as data 75 | func RenderQR(chunk string) string { 76 | qrCode, _ := qr.Encode(chunk, qr.H, qr.Unicode) 77 | qrCode, _ = barcode.Scale(qrCode, 600, 600) 78 | var buff bytes.Buffer 79 | err := png.Encode(&buff, qrCode) 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | encodedString := base64.StdEncoding.EncodeToString(buff.Bytes()) 84 | h := blake2b.Sum256([]byte(chunk)) 85 | fmt.Println("[*] Creating QR code\n DATA_HASH =", hex.EncodeToString(h[:])) //, "\n Payload =", []byte(chunk)) 86 | return "" 87 | } 88 | 89 | // payloadInChunks cuts a string payload into multiple chunks of chunkSize size 90 | func payloadInChunks(longString string, chunkSize int) []string { 91 | var slices []string 92 | lastIndex := 0 93 | lastI := 0 94 | for i := range longString { 95 | if i-lastIndex > chunkSize { 96 | slices = append(slices, longString[lastIndex:lastI]) 97 | lastIndex = lastI 98 | } 99 | lastI = i 100 | } 101 | // handle the leftovers at the end 102 | if len(longString)-lastIndex > chunkSize { 103 | slices = append(slices, longString[lastIndex:lastIndex+chunkSize], longString[lastIndex+chunkSize:]) 104 | } else { 105 | slices = append(slices, longString[lastIndex:]) 106 | } 107 | return slices 108 | } 109 | 110 | // DecodeQRCode returns a string with the base64 encoded payload from the QR code 111 | func DecodeQRCode(img image.Image) string { 112 | buf := new(bytes.Buffer) 113 | err := jpeg.Encode(buf, img, nil) 114 | img, _, err = image.Decode(bytes.NewReader(buf.Bytes())) 115 | if err != nil { 116 | log.Error("image.Decode error: %v\n", err) 117 | return "" 118 | } 119 | qrCodes, err := goqr.Recognize(img) 120 | if err != nil { 121 | // log.Error("Recognize failed: %v\n", err) 122 | return "" 123 | } 124 | var payload string 125 | for _, qrCode := range qrCodes { 126 | payload = payload + string(qrCode.Payload) 127 | } 128 | return payload 129 | } 130 | 131 | /* retrievePayload is the main function that will take the uploaded video, 132 | will extract frames and will call DecodeQRCode() to get the payload. 133 | it will also concatenate all pieces and finally return the full payload. */ 134 | func retrievePayload() bool { 135 | // Split video into frames using ffmpeg. Ideally it should be a module and not an exec.command call 136 | files, err := filepath.Glob("./public/*png") 137 | if err != nil { 138 | panic(err) 139 | } 140 | fmt.Println("[***] Cleaning old files and extracting video frames") 141 | for _, f := range files { 142 | if err := os.Remove(f); err != nil { 143 | panic(err) 144 | } 145 | } 146 | 147 | cmd := exec.Command(ffmpeg, "-i", videoLocation, 148 | "-loglevel", "error", // "-r", "10", 149 | "-qscale:v", ffmpegQuality, 150 | "-vf", ffmpegImageScale, 151 | "-vsync", "vfr", 152 | "./public/%03d.png") 153 | cmd.Stderr = os.Stderr 154 | err = cmd.Run() 155 | if err != nil { 156 | log.Fatal(err) 157 | } 158 | 159 | // Now we need to parse all frames, find if a QR Code is present and extract data from it 160 | var payload string 161 | matches, _ := filepath.Glob("./public/*png") 162 | fmt.Println("[***] Extracting data from", len(matches), "frames, skipping duplicates") 163 | for _, match := range matches { 164 | f, _ := os.Open(match) 165 | img, _, _ := image.Decode(f) 166 | _ = f.Close() 167 | buff := DecodeQRCode(img) 168 | if len(buff) == 0 { 169 | //fmt.Println("[*] Retrieving image from file", match, "- No QR code/data, skipping.") 170 | } else { 171 | result := buff 172 | if len(result) > 0 { 173 | // in case two frames have same QR code and data 174 | duplicate := strings.Contains(payload, result) 175 | if duplicate == false { 176 | payload += result 177 | h := blake2b.Sum256([]byte(result)) 178 | // fmt.Println("[*] Creating QRcode,\n DATA_HASH =" 179 | fmt.Println("[*] Retrieving data from detected QR code in", match, "\n DATA_HASH =", hex.EncodeToString(h[:])) //, 180 | // "\n Payload =", []byte(result)) 181 | } 182 | } 183 | } 184 | } 185 | 186 | var result = true 187 | if len(payload) > 0 { 188 | decoded, _ := base64.StdEncoding.DecodeString(payload) 189 | decompressed, _ := smaz.Decode(nil, decoded) 190 | writePayloadFile(decompressed, retrieved) 191 | h := blake2b.Sum256(decompressed) // content 192 | fmt.Println("[*] Payload saved as ", retrieved, "\nPayload hash", hex.EncodeToString(h[:])) 193 | } else { 194 | log.Info("!!! No Payload retrieved from analyzed frames") 195 | result = false 196 | } 197 | return result 198 | } 199 | 200 | func webService() { 201 | gin.SetMode("release") 202 | //router := gin.Default() 203 | router := gin.New() 204 | router.Static("/process", "./public") 205 | router.GET("/payload", func(c *gin.Context) { 206 | // Source 207 | f, err := os.Open(retrieved) 208 | if err != nil { 209 | log.Fatal(err) 210 | } 211 | defer func(f *os.File) { 212 | err := f.Close() 213 | if err != nil { 214 | 215 | } 216 | }(f) 217 | //Seems these headers needed for some browsers (for example without this headers Chrome will download files as txt) 218 | c.Header("Content-Description", "File Transfer") 219 | c.Header("Content-Transfer-Encoding", "binary") 220 | c.Header("Content-Disposition", "attachment; filename="+retrieved) 221 | c.Header("Content-Type", "application/octet-stream") 222 | c.File(retrieved) 223 | }) 224 | // upload will get a file and save it in ./Public 225 | // test: curl -F 'file=@./1.jpg' http://localhost:9999/upload 226 | router.POST("/upload", func(c *gin.Context) { 227 | // Source 228 | file, err := c.FormFile("file") 229 | if err != nil { 230 | c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) 231 | return 232 | } 233 | 234 | if err := c.SaveUploadedFile(file, videoLocation); err != nil { 235 | c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) 236 | return 237 | } 238 | log.Println("\n[*] File received") 239 | 240 | // processing 241 | result := retrievePayload() 242 | myLink := "No payload retrieved." 243 | if result { 244 | myLink = "download payload" 245 | } 246 | _, err = c.Writer.Write([]byte(myLink)) 247 | if err != nil { 248 | log.Fatal("Cannot serve back the payload file") 249 | } 250 | return 251 | }) 252 | log.Info("Serving on port ", serverPort) 253 | router.Run(":" + serverPort) 254 | } 255 | 256 | func writePayloadFile(payload []byte, filename string) { 257 | err := os.Remove(filename) 258 | if err != nil { 259 | fmt.Println("\n[I] No previous payload file found") 260 | } else { 261 | fmt.Println("\n[I] Deleted previous payload file") 262 | } 263 | // Open a new file for writing only 264 | file, err := os.OpenFile( 265 | filename, 266 | os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 267 | 0666, 268 | ) 269 | if err != nil { 270 | log.Fatal(err) 271 | } 272 | defer func(file *os.File) { 273 | err := file.Close() 274 | if err != nil { 275 | 276 | } 277 | }(file) 278 | 279 | // Write bytes to file 280 | _, err = file.Write(payload) 281 | if err != nil { 282 | log.Fatal(err) 283 | } 284 | } 285 | 286 | //goland:noinspection ALL 287 | func main() { 288 | log.SetFormatter(&log.TextFormatter{ 289 | DisableColors: false, 290 | FullTimestamp: true, 291 | }) 292 | log.SetFormatter(&log.TextFormatter{}) 293 | isServer := flag.Bool("server", false, "Server mode") 294 | isClient := flag.Bool("client", false, "client mode") 295 | isProcessing := flag.Bool("retrievePayload", false, "processing existing video only (debug mode)") 296 | flag.Parse() 297 | 298 | if *isProcessing { 299 | log.Println("Processing only - DEBUG MODE") 300 | _ = retrievePayload() 301 | } else if *isServer { 302 | // webService mode (retrieving data from video) 303 | fmt.Println("[*] Server mode: ON") 304 | webService() 305 | } else if *isClient { 306 | // Client mode (allowing video recording of QR codes) 307 | fmt.Println("[***] Client mode: ON") 308 | writeText, err := os.Open(os.DevNull) 309 | if err != nil { 310 | log.Fatalf("failed to open a null device: %s", err) 311 | } 312 | defer writeText.Close() 313 | io.WriteString(writeText, "Write Text") 314 | 315 | fmt.Println("[*] Loading payload from file") 316 | readText, err := ioutil.ReadAll(os.Stdin) 317 | h := blake2b.Sum256([]byte(readText)) 318 | fmt.Println("Plaintext hash", hex.EncodeToString(h[:])) 319 | if err != nil { 320 | log.Fatalf("failed to open a null device: %s", err) 321 | } 322 | if len(readText) == 0 { 323 | log.Fatalf("No data read from stdin") 324 | } 325 | // Compress, encode, payload in chunks then display the QrCodes 326 | compressed := smaz.Encode(nil, readText) 327 | encoded := base64.StdEncoding.EncodeToString(compressed) 328 | chunks := payloadInChunks(encoded, QRCDataMaxBytes) 329 | fmt.Println("\n[*] Payload will be in", len(chunks), "chunks") 330 | fmt.Println("[***] Start your video, displaying in >", secsBeforeDisplay, "< seconds ****") 331 | fmt.Println() 332 | time.Sleep(secsBeforeDisplay * time.Second) 333 | // Create UI with basic HTML passed via data URI 334 | const html = ` 335 | 336 | goqrexfil PoC 337 |

QR codes streaming starting now

338 | 339 | 340 | ` 341 | ui, error := lorca.New("data:text/html,"+url.PathEscape(html), "", 675, 675) 342 | if error != nil { 343 | log.Fatal("lorca.New():", err) 344 | } 345 | defer ui.Close() 346 | 347 | // Iterate on chunks, generate QR code and display it in UI 348 | for _, chunk := range chunks { 349 | time.Sleep(msBetweenFrames * time.Millisecond) // need some delays to allow video recording and avoid loosing a frame 350 | ui.Load("data:text/html," + url.PathEscape(`
`+RenderQR(chunk)+`
`)) 351 | } 352 | time.Sleep(msBetweenFrames * time.Millisecond) 353 | ui.Load("data:text/html," + url.PathEscape(`

Done

`)) 354 | <-ui.Done() 355 | 356 | } else { 357 | fmt.Println("Please use client or server mode:") 358 | fmt.Println("echo \"data to send\" | ./qr --client\t\tTo use in client mode") 359 | fmt.Println("./goqrexfil --server\t\t\t\t\tTo use as a TLS listener to receive video and retrieve payload.") 360 | fmt.Println() 361 | os.Exit(1) 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /payload/payload.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcefrenchy/goqrexfil/59e352b29d0812a481256cfda3c522f8316ebe31/payload/payload.bin -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /public/render.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 13 | 14 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------