mirror of
https://github.com/wader/fq.git
synced 2024-10-06 00:17:58 +03:00
Compare commits
32 Commits
1be9208238
...
e447271a69
Author | SHA1 | Date | |
---|---|---|---|
|
e447271a69 | ||
|
2bc6c768c7 | ||
|
b3e5be095b | ||
|
6383626aa3 | ||
|
71476743a0 | ||
|
19bd2fbd7c | ||
|
c55e10667c | ||
|
292bd02397 | ||
|
bbdd43f5c8 | ||
|
35a8eddf04 | ||
|
8df50bf62f | ||
|
1ff5a3fadf | ||
|
7566fd9307 | ||
|
f7cbf84469 | ||
|
ab09d3ce6f | ||
|
262d7e1fa1 | ||
|
8bc1a20b2a | ||
|
b0025b64c9 | ||
|
2171924898 | ||
|
44e2385ace | ||
|
0cd90ce0de | ||
|
9775381e05 | ||
|
6f2b59944d | ||
|
cb1557dd65 | ||
|
b818923c26 | ||
|
e97e915352 | ||
|
dff7c3cd74 | ||
|
40f38a5558 | ||
|
61f81fbfda | ||
|
ff65812941 | ||
|
175661d349 | ||
|
bfcaa63192 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GOLANGCILINT_VERSION: "1.58.2"
|
||||
GOLANGCILINT_VERSION: "1.59.1"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.22.3"
|
||||
go-version: "1.22.4"
|
||||
- uses: actions/checkout@v3
|
||||
- uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
@ -47,7 +47,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.22.3"
|
||||
go-version: "1.22.4"
|
||||
- name: Test
|
||||
env:
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.22.3"
|
||||
go-version: "1.22.4"
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
|
@ -1,5 +1,5 @@
|
||||
# bump: docker-golang /FROM golang:([\d.]+)/ docker:golang|^1
|
||||
FROM golang:1.22.3-bookworm AS base
|
||||
FROM golang:1.22.4-bookworm AS base
|
||||
|
||||
# expect is used to test cli
|
||||
RUN \
|
||||
|
2
Makefile
2
Makefile
@ -61,7 +61,7 @@ gogenerate: always
|
||||
lint: always
|
||||
# bump: make-golangci-lint /golangci-lint@v([\d.]+)/ git:https://github.com/golangci/golangci-lint.git|^1
|
||||
# bump: make-golangci-lint link "Release notes" https://github.com/golangci/golangci-lint/releases/tag/v$LATEST
|
||||
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.58.2 run
|
||||
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.1 run
|
||||
|
||||
depgraph.svg: always
|
||||
go run github.com/kisielk/godepgraph@latest github.com/wader/fq | dot -Tsvg -o godepgraph.svg
|
||||
|
@ -169,6 +169,7 @@ For details see [formats.md](doc/formats.md) and [usage.md](doc/usage.md).
|
||||
|
||||
## Presentations
|
||||
|
||||
- [Kodsnack 585 - Polymorfisk JSON](https://kodsnack.se/585/) - Swedish podcast episode about jq and fq
|
||||
- "fq - jq for binary formats" at [FOSDEM 2023](https://fosdem.org/2023/) - [video & slides](https://fosdem.org/2023/schedule/event/bintools_fq/)
|
||||
- "fq - jq for binary formats" at [No time to wait 6](https://mediaarea.net/NoTimeToWait6) - [video](https://www.youtube.com/watch?v=-Pwt5KL-xRs&t=1450s) - [slides](doc/presentations/nttw6/fq-nttw6-slides.pdf)
|
||||
- "fq - jq for binary formats" at [Binary Tools Summit 2022](https://binary-tools.net/summit.html) - [video](https://www.youtube.com/watch?v=GJOq_b0eb-s&list=PLTj8twuHdQz-JcX7k6eOwyVPDB8CyfZc8&index=1) - [slides](doc/presentations/bts2022/fq-bts2022-v1.pdf)
|
||||
|
@ -70,6 +70,7 @@ def formats_sections:
|
||||
| ({} | _help_format_enrich("fq"; $f; false)) as $fhelp
|
||||
| select(has_section($f; $fhelp))
|
||||
| "## \($f.name)"
|
||||
, $f.description + "."
|
||||
, ""
|
||||
, ($fhelp.notes | if . then ., "" else empty end)
|
||||
, if $f.decode_in_arg then
|
||||
|
@ -157,6 +157,7 @@ fq -d bytes 'mp4({force: true})' file.mp4
|
||||
[fq -rn -L . 'include "formats"; formats_sections']: sh-start
|
||||
|
||||
## aac_frame
|
||||
Advanced Audio Coding frame.
|
||||
|
||||
### Options
|
||||
|
||||
@ -177,6 +178,7 @@ Decode value as aac_frame
|
||||
```
|
||||
|
||||
## apple_bookmark
|
||||
Apple BookmarkData.
|
||||
|
||||
Apple's `bookmarkData` format is used to encode information that can be resolved
|
||||
into a `URL` object for a file even if the user moves or renames it. Can also
|
||||
@ -218,6 +220,7 @@ torepr' <sfl2 file>
|
||||
- https://michaellynn.github.io/2015/10/24/apples-bookmarkdata-exposed/
|
||||
|
||||
## asn1_ber
|
||||
ASN1 BER (basic encoding rules, also CER and DER).
|
||||
|
||||
Supports decoding BER, CER and DER (X.690).
|
||||
|
||||
@ -250,6 +253,7 @@ $ fq -d asn1_ber 'torepr as $r | ["version", "modulus", "private_exponent", "pri
|
||||
- https://lapo.it/asn1js/
|
||||
|
||||
## avc_au
|
||||
H.264/AVC Access Unit.
|
||||
|
||||
### Options
|
||||
|
||||
@ -270,6 +274,7 @@ Decode value as avc_au
|
||||
```
|
||||
|
||||
## avi
|
||||
Audio Video Interleaved.
|
||||
|
||||
### Options
|
||||
|
||||
@ -318,6 +323,7 @@ $ fq -o decode_samples=false -o decode_extended_chunks=false d file.avi
|
||||
- [OpenDML AVI File Format Extensions](http://www.jmcgowan.com/odmlff2.pdf)
|
||||
|
||||
## avro_ocf
|
||||
Avro object container file.
|
||||
|
||||
Supports reading Avro Object Container Format (OCF) files based on the 1.11.0 specification.
|
||||
|
||||
@ -337,6 +343,7 @@ xentripetal@fastmail.com
|
||||
[@xentripetal](https://github.com/xentripetal)
|
||||
|
||||
## bencode
|
||||
BitTorrent bencoding.
|
||||
|
||||
### Convert represented value to JSON
|
||||
|
||||
@ -348,6 +355,7 @@ $ fq -d bencode torepr file.torrent
|
||||
- https://wiki.theory.org/BitTorrentSpecification#Bencoding
|
||||
|
||||
## bitcoin_block
|
||||
Bitcoin block.
|
||||
|
||||
### Options
|
||||
|
||||
@ -368,6 +376,7 @@ Decode value as bitcoin_block
|
||||
```
|
||||
|
||||
## bits
|
||||
Raw bits.
|
||||
|
||||
Decode to a slice and indexable binary of bits.
|
||||
|
||||
@ -390,6 +399,7 @@ $ echo 'hello' | fq -c -d bits '[.[range(8)]]'
|
||||
```
|
||||
|
||||
## bplist
|
||||
Apple Binary Property List.
|
||||
|
||||
### Show full decoding
|
||||
```sh
|
||||
@ -454,6 +464,7 @@ bplist> from_ns_keyed_archiver(1)
|
||||
- https://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
|
||||
|
||||
## bson
|
||||
Binary JSON.
|
||||
|
||||
### Limitations
|
||||
|
||||
@ -479,6 +490,7 @@ $ fq -d bson 'torepr | select(.name=="bob")' file.bson
|
||||
- https://bsonspec.org/spec.html
|
||||
|
||||
## bytes
|
||||
Raw bytes.
|
||||
|
||||
Decode to a slice and indexable binary of bytes.
|
||||
|
||||
@ -506,6 +518,7 @@ $ echo 'hello' | fq -d bytes '.[1]'
|
||||
```
|
||||
|
||||
## caff
|
||||
Live2D Cubism archive.
|
||||
|
||||
### Options
|
||||
|
||||
@ -529,6 +542,7 @@ Decode value as caff
|
||||
- [@ronsor](https://github.com/ronsor)
|
||||
|
||||
## cbor
|
||||
Concise Binary Object Representation.
|
||||
|
||||
### Convert represented value to JSON
|
||||
|
||||
@ -541,6 +555,7 @@ $ fq -d cbor torepr file.cbor
|
||||
- https://www.rfc-editor.org/rfc/rfc8949.html
|
||||
|
||||
## csv
|
||||
Comma separated values.
|
||||
|
||||
### Options
|
||||
|
||||
@ -574,6 +589,7 @@ $ fq -d csv '.[0] as $t | .[1:] | map(with_entries(.key = $t[.key]))' file.csv
|
||||
```
|
||||
|
||||
## fit
|
||||
Garmin Flexible and Interoperable Data Transfer.
|
||||
|
||||
### Limitations
|
||||
|
||||
@ -596,6 +612,7 @@ $ fq '[.data_records[] | select(.record_header.message_type == "data").data_mess
|
||||
- https://developer.garmin.com/fit/cookbook/decoding-activity-files/
|
||||
|
||||
## flac_frame
|
||||
FLAC frame.
|
||||
|
||||
### Options
|
||||
|
||||
@ -616,6 +633,7 @@ Decode value as flac_frame
|
||||
```
|
||||
|
||||
## hevc_au
|
||||
H.265/HEVC Access Unit.
|
||||
|
||||
### Options
|
||||
|
||||
@ -636,6 +654,7 @@ Decode value as hevc_au
|
||||
```
|
||||
|
||||
## html
|
||||
HyperText Markup Language.
|
||||
|
||||
### Options
|
||||
|
||||
@ -719,6 +738,7 @@ $ fq -r -o array=true -d html '.. | select(.[0] == "a" and .[1].href)?.[1].href'
|
||||
```
|
||||
|
||||
## leveldb_descriptor
|
||||
LevelDB Descriptor.
|
||||
|
||||
### Limitations
|
||||
|
||||
@ -735,6 +755,7 @@ $ fq -r -o array=true -d html '.. | select(.[0] == "a" and .[1].href)?.[1].href'
|
||||
- https://github.com/google/leveldb/blob/main/db/version_edit.cc
|
||||
|
||||
## leveldb_log
|
||||
LevelDB Log.
|
||||
|
||||
### Limitations
|
||||
|
||||
@ -751,6 +772,7 @@ $ fq -r -o array=true -d html '.. | select(.[0] == "a" and .[1].href)?.[1].href'
|
||||
- https://github.com/google/leveldb/blob/main/db/write_batch.cc
|
||||
|
||||
## leveldb_table
|
||||
LevelDB Table.
|
||||
|
||||
### Limitations
|
||||
|
||||
@ -768,6 +790,7 @@ $ fq -r -o array=true -d html '.. | select(.[0] == "a" and .[1].href)?.[1].href'
|
||||
- https://github.com/google/leveldb/blob/main/doc/index.md
|
||||
|
||||
## luajit
|
||||
LuaJIT 2.0 bytecode.
|
||||
|
||||
### Authors
|
||||
- [@dlatchx](https://github.com/dlatchx)
|
||||
@ -777,6 +800,7 @@ $ fq -r -o array=true -d html '.. | select(.[0] == "a" and .[1].href)?.[1].href'
|
||||
- http://scm.zoomquiet.top/data/20131216145900/index.html
|
||||
|
||||
## macho
|
||||
Mach-O macOS executable.
|
||||
|
||||
Supports decoding vanilla and FAT Mach-O binaries.
|
||||
|
||||
@ -795,12 +819,14 @@ acils@itu.edu.tr
|
||||
[@Akaame](https://github.com/Akaame)
|
||||
|
||||
## markdown
|
||||
Markdown.
|
||||
|
||||
### Array with all level 1 and 2 headers
|
||||
```sh
|
||||
$ fq -d markdown '[.. | select(.type=="heading" and .level<=2)?.children[0]]' file.md
|
||||
```
|
||||
## matroska
|
||||
Matroska file.
|
||||
|
||||
### Options
|
||||
|
||||
@ -840,11 +866,13 @@ $ fq 'grep_by(.id == "Tracks") | matroska_path' file.mkv
|
||||
- https://wiki.xiph.org/MatroskaOpus
|
||||
|
||||
## moc3
|
||||
MOC3 file.
|
||||
|
||||
### Authors
|
||||
- [@ronsor](https://github.com/ronsor)
|
||||
|
||||
## mp3
|
||||
MP3 file.
|
||||
|
||||
### Options
|
||||
|
||||
@ -867,6 +895,7 @@ Decode value as mp3
|
||||
```
|
||||
|
||||
## mp4
|
||||
ISOBMFF, QuickTime and similar.
|
||||
|
||||
### Options
|
||||
|
||||
@ -932,6 +961,7 @@ $ fq 'grep_by(.type == "trak") | mp4_path' file.mp4
|
||||
- [Quicktime file format](https://developer.apple.com/standards/qtff-2001.pdf)
|
||||
|
||||
## msgpack
|
||||
MessagePack.
|
||||
|
||||
### Convert represented value to JSON
|
||||
|
||||
@ -943,6 +973,7 @@ $ fq -d msgpack torepr file.msgpack
|
||||
- https://github.com/msgpack/msgpack/blob/master/spec.md
|
||||
|
||||
## nes
|
||||
iNES/NES 2.0 cartridge ROM format.
|
||||
|
||||
### Limitations
|
||||
|
||||
@ -979,6 +1010,7 @@ $ for line in $(fq -r '.chr_rom[] | nes_tokitty(5)' file.nes);do printf "%b%s" "
|
||||
- https://bugzmanov.github.io/nes_ebook/chapter_6_3.html
|
||||
|
||||
## opentimestamps
|
||||
OpenTimestamps file.
|
||||
|
||||
### View a full OpenTimestamps file
|
||||
|
||||
@ -1006,6 +1038,7 @@ $ fq '.operations | map(select(.attestation_type == "bitcoin")) | length > 0' fi
|
||||
- https://github.com/opentimestamps/python-opentimestamps
|
||||
|
||||
## pcap
|
||||
PCAP packet capture.
|
||||
|
||||
### Build object with number of (reassembled) TCP bytes sent to/from client IP
|
||||
```sh
|
||||
@ -1018,6 +1051,7 @@ $ fq '.tcp_connections | group_by(.client.ip) | map({key: .[0].client.ip, value:
|
||||
}
|
||||
```
|
||||
## pg_btree
|
||||
PostgreSQL btree index file.
|
||||
|
||||
### Options
|
||||
|
||||
@ -1057,6 +1091,7 @@ p.n.safonov@gmail.com
|
||||
### References
|
||||
- https://www.postgresql.org/docs/current/storage-page-layout.html
|
||||
## pg_control
|
||||
PostgreSQL control file.
|
||||
|
||||
### Options
|
||||
|
||||
@ -1096,6 +1131,7 @@ p.n.safonov@gmail.com
|
||||
### References
|
||||
- https://github.com/postgres/postgres/blob/REL_14_2/src/include/catalog/pg_control.h
|
||||
## pg_heap
|
||||
PostgreSQL heap file.
|
||||
|
||||
### Options
|
||||
|
||||
@ -1148,6 +1184,7 @@ p.n.safonov@gmail.com
|
||||
### References
|
||||
- https://www.postgresql.org/docs/current/storage-page-layout.html
|
||||
## protobuf
|
||||
Protobuf.
|
||||
|
||||
### Can decode sub messages
|
||||
|
||||
@ -1159,6 +1196,7 @@ $ fq -d protobuf '.fields[6].wire_value | protobuf | d' file
|
||||
- https://developers.google.com/protocol-buffers/docs/encoding
|
||||
|
||||
## rtmp
|
||||
Real-Time Messaging Protocol.
|
||||
|
||||
Current only supports plain RTMP (not RTMPT or encrypted variants etc) with AMF0 (not AMF3).
|
||||
|
||||
@ -1172,6 +1210,7 @@ fq '.tcp_connections[] | select(.server.port=="rtmp") | d' file.cap
|
||||
- https://rtmp.veriskope.com/pdf/video_file_format_spec_v10.pdf
|
||||
|
||||
## tls
|
||||
Transport layer security.
|
||||
|
||||
### Options
|
||||
|
||||
@ -1319,6 +1358,7 @@ $ fq -o keylog=@traffic.keylog 'first(grep_by(.server.stream | format == "tls")
|
||||
- [RFC 6101: The Secure Sockets Layer (SSL) Protocol Version 3.0](https://www.rfc-editor.org/rfc/rfc)
|
||||
|
||||
## tzif
|
||||
Time Zone Information Format.
|
||||
|
||||
### Get last transition time
|
||||
```sh
|
||||
@ -1339,6 +1379,7 @@ fq '.v2plusdatablock.leap_second_records | length' tziffile
|
||||
- https://datatracker.ietf.org/doc/html/rfc8536
|
||||
|
||||
## wasm
|
||||
WebAssembly Binary Format.
|
||||
|
||||
### Count opcode usage
|
||||
```sh
|
||||
@ -1359,6 +1400,7 @@ $ fq '.sections | {import: map(select(.id == "import_section").content.im.x[].nm
|
||||
- https://webassembly.github.io/spec/core/
|
||||
|
||||
## xml
|
||||
Extensible Markup Language.
|
||||
|
||||
### Options
|
||||
|
||||
@ -1487,6 +1529,7 @@ $ echo '<a><b/><b>bbb</b><c attr="value">ccc</c></a>' | fq -o array=true '.[2][2
|
||||
- [xml.com's Converting Between XML and JSON](https://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html)
|
||||
|
||||
## zip
|
||||
ZIP archive.
|
||||
|
||||
### Options
|
||||
|
||||
|
534
doc/guides/fra-pcap-challange-2021-09/README.md
Normal file
534
doc/guides/fra-pcap-challange-2021-09/README.md
Normal file
@ -0,0 +1,534 @@
|
||||
## Solving part of the FRA pcap challenge 2021-09 using fq
|
||||
|
||||
**CAUTION:** fq is still in early development so things might change but i will try to keep the guide updated.
|
||||
|
||||
This guide will show how to work with fq and how to use the jq language. It will also show some useful things from the standard library of jq and fq. We will walk thru one of the challenges from [FRA challenge](https://challenge.fra.se/) that are challenges [FRA](https://www.fra.se/) (~Swedish NSA) published as part of their recruitment effort. The challenge can be downloaded from [pcap-challenge-2021-09.tgz](https://challenge.fra.se/pcap-challenge-2021-09.tgz) but it can also be found in the same directory as this README file.
|
||||
|
||||
Start by running fq on the file to get an overview:
|
||||
|
||||
```
|
||||
$ fq . pcap-challenge-2021-09.tgz
|
||||
│00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f│0123456789abcdef│.{}: pcap-challenge-2021-09.tgz (gzip)
|
||||
0x0000000│1f 8b │.. │ identification: raw bits (valid)
|
||||
0x0000000│ 08 │ . │ compression_method: "deflate" (8)
|
||||
0x0000000│ 00 │ . │ flags{}:
|
||||
0x0000000│ 00 00 00 00 │ .... │ mtime: 0
|
||||
0x0000000│ 00 │ . │ extra_flags: 0
|
||||
0x0000000│ 03 │ . │ os: "Unix" (3)
|
||||
0x000000│74 72 69 61 6e 67 6c 65 2e 70 63 61 70 00 00 00│triangle.pcap...│ uncompressed{}: (tar)
|
||||
* │until 0x4fafff.7 (end) (5222400) │ │
|
||||
0x0000000│ ec 9c 09 90 1c d5│ ......│ compressed: raw bits
|
||||
0x0000010│79 c7 47 2b a1 15 42 c8 40 44 02 8b 84 57 4b 62│y.G+..B.@D...WKb│
|
||||
* │until 0x3b602.7 (243193) │ │
|
||||
0x003b600│ 1e 41 08 b4 │ .A.. │ crc32: 0xb408411e (valid)
|
||||
0x003b600│ 00 b0 4f 00│ │ ..O.│ │ isize: 5222400
|
||||
```
|
||||
|
||||
The first columns shows the start address of each line, second and third a hex and ASCII dump and the forth a hierarchical tree representation of the decoded values. At the top we see that the file was detected as `gzip`. The tree shows various compression details and that fq has detected that the uncompressed bytes are a `tar` archive, also the `{}` hints that there are more values inside the uncompressed field.
|
||||
|
||||
Of course it's probably easiest to just extract the archive instead using fq but for the case of learning it's probably useful to use something familiar to show how to navigate it using fq.
|
||||
|
||||
Now start an interactive [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) (`-i`):
|
||||
```
|
||||
$ fq -i . pcap-challenge-2021-09.tgz
|
||||
gzip>
|
||||
```
|
||||
|
||||
The First thing we see is a prompt indicating the current "input" which is the root of the gzip file. If we type `.`, it will show the same output as above. `.` is the "identity" function in jq which does nothing, it just outputs what it gets as input. By default fq:s REPL will try to display what the expression is evaluated to in some useful way.
|
||||
|
||||
To recursively display the whole tree we can use `d` (short for `display`) or `f` (short for `display({array_truncate: 0})`) which will do the same but not truncate long arrays.
|
||||
|
||||
Note that the REPL supports auto-completion of functions and fields using tab, there is history support using arrow keys and the prompt supports basic readline shortcuts (same as bash etc).
|
||||
|
||||
To look into the tar archive we can do:
|
||||
|
||||
```
|
||||
gzip> .uncompressed
|
||||
│00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f│0123456789abcdef│.uncompressed{}: (tar)
|
||||
0x000000│74 72 69 61 6e 67 6c 65 2e 70 63 61 70 00 00 00│triangle.pcap...│ files[0:3]:
|
||||
0x000010│00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00│................│
|
||||
* │until 0x4f9dff.7 (5217792) │ │
|
||||
0x4f9e00│00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00│................│ end_marker: raw bits
|
||||
0x4f9e10│00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00│................│
|
||||
* │until 0x4fafff.7 (end) (4608) │ │
|
||||
```
|
||||
|
||||
`files[0:3]` mean that `files` is an array with 3 values, shown using the jq "slice" syntax `start:end`.
|
||||
|
||||
To look at the first file we can use:
|
||||
|
||||
```
|
||||
gzip> .uncompressed.files[0]
|
||||
│00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f│0123456789abcdef│.uncompressed.files[0]{}:
|
||||
0x000000│74 72 69 61 6e 67 6c 65 2e 70 63 61 70 00 00 00│triangle.pcap...│ name: "triangle.pcap"
|
||||
* │until 0x63.7 (100) │ │
|
||||
0x000060│ 30 30 30 30 36 34 34 00 │ 0000644. │ mode: 420 ("0000644")
|
||||
0x000060│ 30 30 30 30│ 0000│ uid: 0 ("0000000")
|
||||
0x000070│30 30 30 00 │000. │
|
||||
0x000070│ 30 30 30 30 30 30 30 00 │ 0000000. │ gid: 0 ("0000000")
|
||||
0x000070│ 30 30 30 32│ 0002│ size: 5207688 ("00023673210")
|
||||
0x000080│33 36 37 33 32 31 30 00 │3673210. │
|
||||
0x000080│ 31 34 31 30 35 32 31 35│ 14105215│ mtime: 1628773280 ("14105215640")
|
||||
0x000090│36 34 30 00 │640. │
|
||||
0x000090│ 30 31 32 32 34 32 00 20 │ 012242. │ chksum: 5282 ("012242")
|
||||
0x000090│ 30 │ 0 │ typeflag: "0"
|
||||
0x000090│ 00 00 00│ ...│ linkname: ""
|
||||
0x0000a0│00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00│................│
|
||||
* │until 0x100.7 (100) │ │
|
||||
0x000100│ 75 73 74 61 72 20 │ ustar │ magic: "ustar"
|
||||
0x000100│ 20 00 │ . │ version: " "
|
||||
0x000100│ 72 6f 6f 74 00 00 00│ root...│ uname: "root"
|
||||
0x000110│00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00│................│
|
||||
0x000120│00 00 00 00 00 00 00 00 00 │......... │
|
||||
0x000120│ 72 6f 6f 74 00 00 00│ root...│ gname: "root"
|
||||
0x000130│00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00│................│
|
||||
0x000140│00 00 00 00 00 00 00 00 00 │......... │
|
||||
0x000140│ 00 00 00 00 00 00 00│ .......│ devmajor: ""
|
||||
0x000150│00 │. │
|
||||
0x000150│ 00 00 00 00 00 00 00 00 │ ........ │ devminor: ""
|
||||
0x000150│ 00 00 00 00 00 00 00│ .......│ prefix: ""
|
||||
0x000160│00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00│................│
|
||||
* │until 0x1f3.7 (155) │ │
|
||||
0x0001f0│ 00 00 00 00 00 00 00 00 00 00 00 00│ ............│ header_block_padding: raw bits (all zero)
|
||||
0x000200│d4 c3 b2 a1 02 00 04 00 00 00 00 00 00 00 00 00│................│ data{}: (pcap)
|
||||
* │until 0x4f7887.7 (5207688) │ │
|
||||
0x4f7880│ 00 00 00 00 00 00 00 00│ ........│ data_block_padding: raw bits (all zero)
|
||||
0x4f7890│00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00│................│
|
||||
* │until 0x4f79ff.7 (376) │ │
|
||||
```
|
||||
|
||||
It seems to be a PCAP file. To iterate and display all file names we caN do:
|
||||
|
||||
```
|
||||
gzip> .uncompressed.files[].name
|
||||
│00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f│0123456789abcdef│
|
||||
0x00│74 72 69 61 6e 67 6c 65 2e 70 63 61 70 00 00 00│triangle.pcap...│.uncompressed.files[0].name: "triangle.pcap"
|
||||
* │until 0x63.7 (100) │ │
|
||||
│00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f│0123456789abcdef│
|
||||
0x4f7a00│74 72 69 61 6e 67 6c 65 73 2e 70 79 00 00 00 00│triangles.py....│.uncompressed.files[1].name: "triangles.py"
|
||||
* │until 0x4f7a63.7 (100) │ │
|
||||
│00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f│0123456789abcdef│
|
||||
0x4f9a00│52 45 41 44 4d 45 2e 74 78 74 00 00 00 00 00 00│README.txt......│.uncompressed.files[2].name: "README.txt"
|
||||
* │until 0x4f9a63.7 (100) │ │
|
||||
```
|
||||
|
||||
This uses the jq "each" operator `[]` which will output all values in an array (or object), then we use `.name` on each output to get the name.
|
||||
|
||||
If we want to access just the name and not display the hexdump columns etc, we can use the "pipe" operator and filter each value thru `tovalue` which will convert it into some JSON compatible.
|
||||
|
||||
```
|
||||
gzip> .uncompressed.files[].name | tovalue
|
||||
"triangle.pcap"
|
||||
"triangles.py"
|
||||
"README.txt"
|
||||
```
|
||||
|
||||
Or if we want to have the names in an array:
|
||||
|
||||
```
|
||||
gzip> [.uncompressed.files[].name]
|
||||
[
|
||||
"triangle.pcap",
|
||||
"triangles.py",
|
||||
"README.txt"
|
||||
]
|
||||
```
|
||||
|
||||
Here we use the `[...]` array constructor which will collect all outputs from the expression between the brackets. `tovalue` is not needed in this case as the `.name` decode values in the array knows how to behave as JSON values. But note that the values still are decode values. Consider the difference:
|
||||
|
||||
```
|
||||
gzip> [.uncompressed.files[].name][1]
|
||||
│00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f│0123456789abcdef│
|
||||
0x4f7a00│74 72 69 61 6e 67 6c 65 73 2e 70 79 00 00 00 00│triangles.py....│.uncompressed.files[1].name: "triangles.py"
|
||||
* │until 0x4f7a63.7 (100) │ │
|
||||
gzip> [.uncompressed.files[].name | tovalue][1]
|
||||
"triangles.py"
|
||||
```
|
||||
|
||||
As a side note jq's standard library has a `map` function that does exactly this `.uncompressed.files | map(.name)`. To learn more one can look at [how map is implemented](https://github.com/stedolan/jq/blob/master/src/builtin.jq), look for `def map(f):`.
|
||||
|
||||
Back to the challenge and have a look at content of the `README.txt` file:
|
||||
|
||||
```
|
||||
gzip> .uncompressed.files[2].data | println
|
||||
FRA Pcap Challenge 2021-09
|
||||
==========================
|
||||
|
||||
1. Spara skärmdump av rymdskeppet
|
||||
2. Spara skärmdump av när skeppet flyger runt
|
||||
3. Spara skärmdump av en sten och en blomma
|
||||
4. Spara en skärmdump som täcker hela området som skeppet flugit
|
||||
|
||||
Lycka till!
|
||||
```
|
||||
|
||||
It's instructions in Swedish telling us to take various screenshots of a space ship flying around in space.
|
||||
|
||||
Here we used `println` which convert it's input to a string and prints it to stdout (and outputs nothing, more on that later).
|
||||
|
||||
Let's have a look at the PCAP file:
|
||||
|
||||
```
|
||||
gzip> .uncompressed.files[0].data
|
||||
│00 01 02 03 04 05 06 07 08 09 0a 0b│0123456789ab│.uncompressed.files[0].data{}: (pcap)
|
||||
0x0001f8│ d4 c3 b2 a1│ ....│ magic: "little_endian" (0xd4c3b2a1) (valid)
|
||||
0x000204│02 00 │.. │ version_major: 2
|
||||
0x000204│ 04 00 │ .. │ version_minor: 4
|
||||
0x000204│ 00 00 00 00 │ .... │ thiszone: 0
|
||||
0x000204│ 00 00 00 00│ ....│ sigfigs: 0
|
||||
0x000210│00 00 04 00 │.... │ snaplen: 262144
|
||||
0x000210│ 71 00 00 00 │ q... │ network: "linux_sll" (113) (Linux "cooked" capture encapsulation)
|
||||
0x000210│ 92 1a 15 61│ ...a│ packets[0:5705]:
|
||||
0x00021c│7a e4 07 00 4c 00 00 00 4c 00 00 00│z...L...L...│
|
||||
* │until 0x4f7887.7 (5207664) │ │
|
||||
│ │ │ ipv4_reassembled[0:0]:
|
||||
│ │ │ tcp_connections[0:1]:
|
||||
```
|
||||
|
||||
It has 5705 captured packets, no IPv4 fragments and there was one reassembled TCP connection.
|
||||
|
||||
First time I ran fq on this pcap it could not decode much as it did not support "linux_ssl". It was not that much work to add support, which is a reminder that if fq does not support something it might not be that far away from supporting it.
|
||||
|
||||
Now have a look at the streams from the TCP connection:
|
||||
|
||||
```
|
||||
gzip> .uncompressed.files[0].data.tcp_connections[0]
|
||||
│00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f│0123456789abcdef│.uncompressed.files[0].data.tcp_connections[0]{}:
|
||||
0x000000│7a 7a 7a 6d 6d 6d 6d 6d 6d 6d 6d 6d 6d 6d 6d 6d│zzzmmmmmmmmmmmmm│ client_stream: raw bits
|
||||
* │until 0x31e.7 (end) (799) │ │
|
||||
0x000000│7b 22 6d 73 67 5f 74 79 70 65 22 3a 20 22 63 6f│{"msg_type": "co│ server_stream: raw bits
|
||||
* │until 0x48234c.7 (end) (4727629) │ │
|
||||
│ │ │ source_ip: "127.0.0.1"
|
||||
│ │ │ source_port: 57040
|
||||
│ │ │ destination_ip: "127.0.0.1"
|
||||
│ │ │ destination_port: 4372
|
||||
```
|
||||
|
||||
Looks like the client sent lots of "z" and "m ", maybe key strokes to control a ship? The server has responded with something that looks like 4727629 bytes of JSON. it's probably a bad idea to display the whole string, let's show the first 2000 bytes as a string.
|
||||
|
||||
```
|
||||
gzip> .uncompressed.files[0].data.tcp_connections[0].server_stream[0:2000]
|
||||
"{\"msg_type\": \"code\", \"encoding\": \"ascii\", \"data\": \"class Player(Object):\\n def __init__(self, x, y):\\n
|
||||
Object.__init__(self, x, y)\\n\\n self.MAX_ROTATION_SPEED=7\\n self.MAX_SPEED=0.1\\n self.MAX_THRUS
|
||||
T=0.01\\n self.SPEED_REDUCTION=1.01\\n\\n d
|
||||
ef render(self, buf, center_x, center_y):\\n self.buf=buf\\n self.img=Image.new(mode='RGB', size=(IMAGE_RE
|
||||
SOLUTION['X'], IMAGE_RESOLUTION['Y']))\\n\\n
|
||||
d=ImageDraw.Draw(self.img)\\n\\n d.polygon(((IMAGE_RESOLUTION['X']/2, IMAGE_RESOLUTION['Y']/2-50),
|
||||
(IMAGE_RESOLUTION['X']/2+25, IMAGE_RESOLUTION
|
||||
['Y']/2+50), (IMAGE_RESOLUTION['X']/2, IMAGE_RESOLUTION['Y']/2+20), (IMAGE_RESOLUTIO
|
||||
N['X']/2-25, IMAGE_RESOLUTION['Y']/2+50),
|
||||
), outline='green', fill='cyan')\\n\\n self.image2ascii(self.img, self.game2image(self.x, sel
|
||||
f.y, center_x, center_y))\\n return self
|
||||
.buf\\n \", \"md5\": \"5997afe4ac36467ffa0b4a7875a3627a\"}\n{\"msg_type\": \"code\", \"encoding\": \"long_xor\", \"da
|
||||
ta\": \"241e00051a542b290c194b2a251804151d5d434c4f52434523170756362b102806063c3a6f01041a0f58593e43521a4967110e1a06060a7b
|
||||
3455041722170f5145545e31071b1700602f4d56191b10281b015e3e74474d47594455775d424f5477424d415c584076325b596f6752415649545966
|
||||
2010090024064f29361d172f1b2d3c4d34170d104554016a4f0b4a6f67524156495459661c170f0369020e1f07000a7b1f1d0a0b33016b5649545966
|
||||
4f524316221e07580a1b15291d015e06281e0e041a7e59664f524345675212130512573400065e17261c0519045a0b2701160a0b335a515a5a41406f
|
||||
65784345675205130f540b23011606176f01041a0f5859241a144f4524170f020c06263e4352000029060404360d507c655243456752415649071c2a
|
||||
095c0110214f03030f7e59664f524345675212130512572f02155e2c2a130613471a1c31471f0c01224f46242e365e6a4f010a1f224f493f24353e03
|
||||
30202636083e3422203b371d482a44386b52283b28333c193d37302a0b27353f263a226136553e4c6e786b56495459664f5243017a3b0c170e113d34
|
||||
0e054d213513165e1a111520411b0e026e786b7c495459664f524345241d0e040d07441d473b2e2400373e242c27360a3a262a2a0"
|
||||
```
|
||||
|
||||
We use the slice syntax to slice from character 0 to 2000. We use the word characters (UTF-8 codepoints) here as raw bits are by default represented as strings in fq. To get access to raw bits there are functions like `tobytes` etc.
|
||||
|
||||
There are two `{"msg_type": ... }` and the first one ends with `... }\n` so it's probably JSON lines. Let's split on `\n` and have a look:
|
||||
|
||||
```
|
||||
gzip> .uncompressed.files[0].data.tcp_connections[0].server_stream | split("\n") | .[0,1,-2,-1]
|
||||
"{\"msg_type\": \"code\", \"encoding\": \"ascii\", \"data\": \"class Player(Object):\\n def __init__(self, x, y):\\n Object.__init__(self, x, y)\\n\\n self.MAX_ROTATION_SPEED=7\\n self.MAX_SPEED=0.1\\n self.MAX_THRUST=0.01\\n self.SPEED_REDUCTION=1.01\\n\\n def render(self, buf, center_x, center_y):\\n self.buf=buf\\n self.img=Image.new(mode='RGB', size=(IMAGE_RESOLUTION['X'], IMAGE_RESOLUTION['Y']))\\n\\n d=ImageDraw.Draw(self.img)\\n\\n d.polygon(((IMAGE_RESOLUTION['X']/2, IMAGE_RESOLUTION['Y']/2-50), (IMAGE_RESOLUTION['X']/2+25, IMAGE_RESOLUTION['Y']/2+50), (IMAGE_RESOLUTION['X']/2, IMAGE_RESOLUTION['Y']/2+20), (IMAGE_RESOLUTION['X']/2-25, IMAGE_RESOLUTION['Y']/2+50), ), outline='green', fill='cyan')\\n\\n self.image2ascii(self.img, self.game2image(self.x, self.y, center_x, center_y))\\n return self.buf\\n \", \"md5\": \"5997afe4ac36467ffa0b4a7875a3627a\"}"
|
||||
"{\"msg_type\": \"code\", \"encoding\": \"long_xor\", \"data\": \"241e00051a542b290c194b2a251804151d5d434c4f52434523170756362b102806063c3a6f01041a0f58593e43521a4967110e1a06060a7b3455041722170f5145545e31071b1700602f4d56191b10281b015e3e74474d47594455775d424f5477424d415c584076325b596f67524156495459662010090024064f29361d172f1b2d3c4d34170d104554016a4f0b4a6f67524156495459661c170f0369020e1f07000a7b1f1d0a0b33016b56495459664f524316221e07580a1b15291d015e06281e0e041a7e59664f524345675212130512573400065e17261c0519045a0b2701160a0b335a515a5a41406f65784345675205130f540b23011606176f01041a0f5859241a144f4524170f020c06263e4352000029060404360d507c655243456752415649071c2a095c0110214f03030f7e59664f524345675212130512572f02155e2c2a130613471a1c31471f0c01224f46242e365e6a4f010a1f224f493f24353e0330202636083e3422203b371d482a44386b52283b28333c193d37302a0b27353f263a226136553e4c6e786b56495459664f5243017a3b0c170e113d340e054d213513165e1a111520411b0e026e786b7c495459664f524345241d0e040d07441d473b2e2400373e242c27360a3a262a2a0929462e4e29567444130e152b580c171d1c5735061c4b172616485a203938012a2d3120143d2d233d3d360834553a421a5d535b0819092a451f02112f5c02191a5c0b270b5b4a45211d13564106182243130e152b5b411f0754032f1f5a380c6d404b1b080011681f1b4c09221c49050c181f681f1d0a0b330148560f1b0b6606520a0b670000180e11512a0a1c4b16221e0758191b10281b014a4c1a5e41050c181f681f1d0a0b3301482b637e59664f52434567520558191b153f081d0d4d241d0e040d0755664f52434567524156495459664f5243452807151a001a1c7b1c170f0369110e1a06060a1d5f2f4f45211b0d1a54071c2a095c000a2b1d1305324524664678696f67524156495459661c170f03691b0c170e114b271c110a0c6f01041a0f5a102b085e4316221e07580e1514235d1b0e04201749050c181f68175e4316221e0758105859250a1c1700352d195a49171c281b17113a3e5b487c495459664f524345351715031b1a59350a1e054b2507077c635459664f\", \"md5\": \"e9514a2de33051b9c6174d31e35b7584\"}"
|
||||
"{\"msg_type\": \"update\", \"encoding\": \"ascii\", \"data\": \"{\\\"id\\\": 1, \\\"type\\\": \\\"Player\\\", \\\"x\\\": 253.65325300861684, \\\"y\\\": 352.330474276039, \\\"rot\\\": 308}\", \"md5\": \"caa8fb32ac8426f29d32da8c46b843ad\"}"
|
||||
""
|
||||
```
|
||||
|
||||
`split("\n")` splits a string on some boundary, "\n" in this case, and output AN array with all the parts. We then use `.[0,1,-2,-1]` to get the first, second, second from the end and last string from the array (negative indexes counts from the end).
|
||||
|
||||
Here we VERB MISSING the output operator `,` which is a way in jq to return or "output" more than one value. What happens is that for each value `0`,`1`, `-2` and `-1` the program "forks" and continueS to run and then aT some point it will "backtrack" back to THE next value and so on. To learn more one can read all about it on
|
||||
[jq internals](https://github.com/stedolan/jq/wiki/Internals%3A-backtracking).
|
||||
|
||||
This is also how jq arrays are expressed, remember `[...]`? let's try `[0,1,-2,-1]`:
|
||||
|
||||
```
|
||||
gzip> [0,1,-2,-1]
|
||||
[
|
||||
0,
|
||||
1,
|
||||
-2,
|
||||
-1
|
||||
]
|
||||
```
|
||||
|
||||
`[...]` "collects" the output from the expression `0,1-2,-1` and to show that what is inside the brackets is just an expression we can do:
|
||||
|
||||
```
|
||||
gzip> [(0,(1,2))]
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2
|
||||
]
|
||||
```
|
||||
|
||||
and to go full circle we can use a multiple output expression as a array index:
|
||||
|
||||
```
|
||||
gzip> [1,2,3][0,1,2]
|
||||
1
|
||||
2
|
||||
3
|
||||
```
|
||||
|
||||
first we build an array by collecting `1,2,3` and then we index into it using `0,1,2`, getting the values at index `0`, `1` and `2`.
|
||||
|
||||
Now back to the JSON lines. We see that the last is just an empty string, probably because the streams end with a new line. Let's filter it out, convert the other lines into useable values and then make things a bit more comfortable by starting a new REPL.
|
||||
|
||||
```
|
||||
gzip> .uncompressed.files[0].data.tcp_connections[0].server_stream | split("\n") | map(select(. != "") | fromjson) | repl
|
||||
> [object, ...][13842][]>
|
||||
```
|
||||
|
||||
We `map` here to do something for each line. `select` is A function that given a condition will output its input or otherwise it will return `empty`, in this case removing all empty strings. Then we pipe all the non-empty lines to `fromjson`, which converts JSON represention into a jq value. Lastly we pipe the result to `repl`, which will start a new nested REPL.
|
||||
|
||||
The prompt tells us that the input is an array with 13842 values, the first value is an object.
|
||||
|
||||
Let's have look using `.[0,1,-2,-1]` again. `.` here is the current input.
|
||||
|
||||
```
|
||||
> [object, ...][13842][]> .[0,1,-2,-1]
|
||||
{
|
||||
"data": "class Player(Object):\n def __init__(self, x, y):\n Object.__init__(self, x, y)\n\n self.MAX_ROTATION_SPEED=7\n self.MAX_SPEED=0.1\n self.MAX_THRUST=0.01\n self.SPEED_REDUCTION=1.01\n\n def render(self, buf, center_x, center_y):\n self.buf=buf\n self.img=Image.new(mode='RGB', size=(IMAGE_RESOLUTION['X'], IMAGE_RESOLUTION['Y']))\n\n d=ImageDraw.Draw(self.img)\n\n d.polygon(((IMAGE_RESOLUTION['X']/2, IMAGE_RESOLUTION['Y']/2-50), (IMAGE_RESOLUTION['X']/2+25, IMAGE_RESOLUTION['Y']/2+50), (IMAGE_RESOLUTION['X']/2, IMAGE_RESOLUTION['Y']/2+20), (IMAGE_RESOLUTION['X']/2-25, IMAGE_RESOLUTION['Y']/2+50), ), outline='green', fill='cyan')\n\n self.image2ascii(self.img, self.game2image(self.x, self.y, center_x, center_y))\n return self.buf\n ",
|
||||
"encoding": "ascii",
|
||||
"md5": "5997afe4ac36467ffa0b4a7875a3627a",
|
||||
"msg_type": "code"
|
||||
}
|
||||
{
|
||||
"data": "241e00051a542b290c194b2a251804151d5d434c4f52434523170756362b102806063c3a6f01041a0f58593e43521a4967110e1a06060a7b3455041722170f5145545e31071b1700602f4d56191b10281b015e3e74474d47594455775d424f5477424d415c584076325b596f67524156495459662010090024064f29361d172f1b2d3c4d34170d104554016a4f0b4a6f67524156495459661c170f0369020e1f07000a7b1f1d0a0b33016b56495459664f524316221e07580a1b15291d015e06281e0e041a7e59664f524345675212130512573400065e17261c0519045a0b2701160a0b335a515a5a41406f65784345675205130f540b23011606176f01041a0f5859241a144f4524170f020c06263e4352000029060404360d507c655243456752415649071c2a095c0110214f03030f7e59664f524345675212130512572f02155e2c2a130613471a1c31471f0c01224f46242e365e6a4f010a1f224f493f24353e0330202636083e3422203b371d482a44386b52283b28333c193d37302a0b27353f263a226136553e4c6e786b56495459664f5243017a3b0c170e113d340e054d213513165e1a111520411b0e026e786b7c495459664f524345241d0e040d07441d473b2e2400373e242c27360a3a262a2a0929462e4e29567444130e152b580c171d1c5735061c4b172616485a203938012a2d3120143d2d233d3d360834553a421a5d535b0819092a451f02112f5c02191a5c0b270b5b4a45211d13564106182243130e152b5b411f0754032f1f5a380c6d404b1b080011681f1b4c09221c49050c181f681f1d0a0b330148560f1b0b6606520a0b670000180e11512a0a1c4b16221e0758191b10281b014a4c1a5e41050c181f681f1d0a0b3301482b637e59664f52434567520558191b153f081d0d4d241d0e040d0755664f52434567524156495459664f5243452807151a001a1c7b1c170f0369110e1a06060a1d5f2f4f45211b0d1a54071c2a095c000a2b1d1305324524664678696f67524156495459661c170f03691b0c170e114b271c110a0c6f01041a0f5a102b085e4316221e07580e1514235d1b0e04201749050c181f68175e4316221e0758105859250a1c1700352d195a49171c281b17113a3e5b487c495459664f524345351715031b1a59350a1e054b2507077c635459664f",
|
||||
"encoding": "long_xor",
|
||||
"md5": "e9514a2de33051b9c6174d31e35b7584",
|
||||
"msg_type": "code"
|
||||
}
|
||||
{
|
||||
"data": "3c652e23657d6776777077757e6b6765333e3722657d6765012b28302235656b67653f657d6775747769776b67653e657d6774737769776b6765352833657d67773a",
|
||||
"encoding": "xor",
|
||||
"md5": "a80484425bfb3c32a6108ac3d37bd3b8",
|
||||
"msg_type": "update"
|
||||
}
|
||||
{
|
||||
"data": "{\"id\": 1, \"type\": \"Player\", \"x\": 253.65325300861684, \"y\": 352.330474276039, \"rot\": 308}",
|
||||
"encoding": "ascii",
|
||||
"md5": "caa8fb32ac8426f29d32da8c46b843ad",
|
||||
"msg_type": "update"
|
||||
}
|
||||
```
|
||||
|
||||
Looks like nested JSON and some of the values are encrypted.
|
||||
|
||||
Maybe we can get some overview by filtering out things that seems to change in each message and see what is left:
|
||||
|
||||
```
|
||||
> [object, ...][13842]> count_by(del(.md5) | del(.data))
|
||||
[
|
||||
[
|
||||
{
|
||||
"encoding": "ascii",
|
||||
"msg_type": "code"
|
||||
},
|
||||
1
|
||||
],
|
||||
[
|
||||
{
|
||||
"encoding": "ascii",
|
||||
"msg_type": "update"
|
||||
},
|
||||
708
|
||||
],
|
||||
[
|
||||
{
|
||||
"encoding": "long_xor",
|
||||
"msg_type": "code"
|
||||
},
|
||||
2
|
||||
],
|
||||
[
|
||||
{
|
||||
"encoding": "xor",
|
||||
"msg_type": "update"
|
||||
},
|
||||
13131
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
Here we use `count_by` which is a fq function similar to the jq function `unique_by` but instead of groping all unique values it just counts them, returning an array with `[unique value, count]` pairs. In jq `_by` is convension used by some functions to allow ad-hoc transform something before using it. For example `count` count number of unique values but `count_by` will count unique values after from transformation, in this case the transformation `del(.md5) | del(.data)` which will remove the keys `md5` and `data` from an object.
|
||||
|
||||
How does the first two "xor" encoded messages looks like:
|
||||
|
||||
```
|
||||
> [object, ...][0:13842]> map(select(.encoding == "xor")) | .[0:2]
|
||||
[
|
||||
{
|
||||
"data": "3c652e23657d6774757e7f767f6b6765333e3722657d67651528242c656b67653f657d67757e776b67653e657d677671776b6765352833657d677f736b676524282b283534657d671c652035222229656b6765302f2e3322651a6b676537282e293334657d671c74776b6773776b6773776b6776776b6773771a3a",
|
||||
"encoding": "xor",
|
||||
"md5": "2074c8a4effcbaa0ceec11754cfa66c3",
|
||||
"msg_type": "update"
|
||||
},
|
||||
{
|
||||
"data": "3c652e23657d67717373707f736b6765333e3722657d67651528242c656b67653f657d677474776b67653e657d67767f776b6765352833657d677476716b676524282b283534657d671c652035222229656b6765302f2e3322651a6b676537282e293334657d671c74776b6773776b6773776b6776776b6773771a3a",
|
||||
"encoding": "xor",
|
||||
"md5": "8f772df87dd20c5a41109b6ed84efb46",
|
||||
"msg_type": "update"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The common `3c652e23657d67` prefix is suspicious and looks hex encoded and probably some encrypted form of `{"id": ` judging by how other "update" messages look. There was "long_xor" so maybe this is just per byte xor? XOR is symmetric so we should get the key if we encrypt some known plaintext with its encrypted version.
|
||||
|
||||
```
|
||||
> [object, ...][13842]> bxor(("{" | explode)[0]; ("3c" | hex | explode)[0])
|
||||
71
|
||||
```
|
||||
|
||||
`explode` is a jq functions that turn a string into an array of its codepoints. `hex` decodes a hex string into a bytes buffer but we use it as string here. We then bitwise XOR using `bxor` and get the key `71`.
|
||||
|
||||
Notice that jq uses the argument separator `;` not `,`.
|
||||
|
||||
Now try bitwise XOR all codepoints with `71`:
|
||||
|
||||
```
|
||||
> [object, ...][13842]> .[-2].data | hex | explode | map(bxor(.; 71)) | implode
|
||||
"{\"id\": 107029, \"type\": \"Flower\", \"x\": 230.0, \"y\": 340.0, \"rot\": 0}"
|
||||
```
|
||||
|
||||
Ok that looks like JSON to me. Now let's have a look at "long_xor" with "msg_type" "code", maybe it starts with `class `?.
|
||||
|
||||
```
|
||||
> [object, ...][13842]> ["class " | explode, ("241e00051a54" | hex | explode)] | transpose | map(bxor(.[0]; .[1])) | implode
|
||||
"Gravit"
|
||||
```
|
||||
|
||||
That looks like it could be a start of a key. I noticed earlier while poking around that the hex decoded data for a "code" message has parts that looks like english text.
|
||||
|
||||
```
|
||||
> [object, ...][0:13842]> .[1].data | hex | tostring | scan("\\w*force\\w*"; "i")
|
||||
"YfORCEgR"
|
||||
"YfORCEgR"
|
||||
"ITYfORCE"
|
||||
"YfORCEgR"
|
||||
"UfORCEgRAVITYfORCE"
|
||||
"ITYfORCE5"
|
||||
```
|
||||
|
||||
`scan` is jq function that outputs all non-overlapping strings matching a regexp. `"i"` is to match case-insensitive.
|
||||
|
||||
A good guess is probably that the key is `"GravityForce"` repeated. Why it shows up and have weird casing is probably because the plain text has long streaks of whitespace `0x32` which happens to be the bit in encoding ASCII encoding changing between upper/lower case.
|
||||
|
||||
Let's try decrypt it using it:
|
||||
|
||||
```
|
||||
> [object, ...][13842]> .[1].data | hex | [., ("GravityForce"*100)[0:length]] | map(explode) | transpose | map(bxor(.[0]; .[1])) | implode
|
||||
"class Rock(Object):\n def __init__(self, x, y, colors=['green', 'white'], points=[35,100,120,100,75,90]):\n Object.__init__(self, x, y)\n self.points=points\n self.colors=colors\n self.rot=random.randint(0,359)\n\n def render(self, buf, center_x, center_y):\n self.buf=buf\n self.img=Image.new(mode='RGB', size=(IMAGE_RESOLUTION['X'], IMAGE_RESOLUTION['Y']))\n\n d=ImageDraw.Draw(self.img)\n\n\n coords=[(IMAGE_RESOLUTION['X']/2+ampl*math.sin(rad),IMAGE_RESOLUTION['Y']/2-ampl*math.cos(rad)) for (rad,ampl) in zip([i*2*math.pi/len(self.points) for i in range(len(self.points))], self.points)]\n\n d.polygon(coords, outline=self.colors[0], fill=self.colors[1] )\n\n\n self.image2ascii(self.img, self.game2image(self.x, self.y, center_x, center_y))\n return self.buf\n\n "
|
||||
```
|
||||
|
||||
Looks like python to me.
|
||||
|
||||
`("GravityForce"*100)[0:length]` is a quick trick to make sure the key is repeated and has the same length as the encrypted message. `length` is a function that returns the length of its input.
|
||||
|
||||
Now decrypt all data strings and use `fromjson` again and start a new REPL:
|
||||
|
||||
```
|
||||
> [object, ...][0:13842]> map(if .encoding == "xor" then .data |= (hex | explode | map(bxor(.; 71)) | implode) elif .encoding == "long_xor" then .data |= (hex | [., ("GravityForce"*100)[0:length]] | map(explode) | transpose | map(bxor(.[0]; .[1])) | implode) end | if .msg_type == "update" then .data |= fromjson end) | repl
|
||||
>> [object, ...][0:13842]>
|
||||
```
|
||||
|
||||
We can break this down a bit:
|
||||
```
|
||||
map(
|
||||
( if .encoding == "xor" then
|
||||
.data |= (hex | explode | map(bxor(.; 71)) | implode)
|
||||
elif .encoding == "long_xor" then
|
||||
.data |= (hex | [., ("GravityForce"*100)[0:length]] | map(explode) | transpose | map(bxor(.[0]; .[1])) | implode)
|
||||
end
|
||||
| if .msg_type == "update" then .data |= fromjson end
|
||||
)
|
||||
) | repl
|
||||
```
|
||||
|
||||
TODO: break this down more
|
||||
|
||||
A few new things here. `if` is A plain old if-statement, conditionally do something, that can have optional `elif`/`else` branches. `.key |= ...` is a shorthand for `.key = (.key | ...)` which will update a keys value in an object using the current value as input.
|
||||
|
||||
Now have a look at `.[0,1,-2,-1]` again:
|
||||
|
||||
```
|
||||
>> [object, ...][0:13842]> .[0,1,-2,-1]
|
||||
{
|
||||
"data": "class Player(Object):\n def __init__(self, x, y):\n Object.__init__(self, x, y)\n\n self.MAX_ROTATION_SPEED=7\n self.MAX_SPEED=0.1\n self.MAX_THRUST=0.01\n self.SPEED_REDUCTION=1.01\n\n def render(self, buf, center_x, center_y):\n self.buf=buf\n self.img=Image.new(mode='RGB', size=(IMAGE_RESOLUTION['X'], IMAGE_RESOLUTION['Y']))\n\n d=ImageDraw.Draw(self.img)\n\n d.polygon(((IMAGE_RESOLUTION['X']/2, IMAGE_RESOLUTION['Y']/2-50), (IMAGE_RESOLUTION['X']/2+25, IMAGE_RESOLUTION['Y']/2+50), (IMAGE_RESOLUTION['X']/2, IMAGE_RESOLUTION['Y']/2+20), (IMAGE_RESOLUTION['X']/2-25, IMAGE_RESOLUTION['Y']/2+50), ), outline='green', fill='cyan')\n\n self.image2ascii(self.img, self.game2image(self.x, self.y, center_x, center_y))\n return self.buf\n ",
|
||||
"encoding": "ascii",
|
||||
"md5": "5997afe4ac36467ffa0b4a7875a3627a",
|
||||
"msg_type": "code"
|
||||
}
|
||||
{
|
||||
"data": "class Rock(Object):\n def __init__(self, x, y, colors=['green', 'white'], points=[35,100,120,100,75,90]):\n Object.__init__(self, x, y)\n self.points=points\n self.colors=colors\n self.rot=random.randint(0,359)\n\n def render(self, buf, center_x, center_y):\n self.buf=buf\n self.img=Image.new(mode='RGB', size=(IMAGE_RESOLUTION['X'], IMAGE_RESOLUTION['Y']))\n\n d=ImageDraw.Draw(self.img)\n\n\n coords=[(IMAGE_RESOLUTION['X']/2+ampl*math.sin(rad),IMAGE_RESOLUTION['Y']/2-ampl*math.cos(rad)) for (rad,ampl) in zip([i*2*math.pi/len(self.points) for i in range(len(self.points))], self.points)]\n\n d.polygon(coords, outline=self.colors[0], fill=self.colors[1] )\n\n\n self.image2ascii(self.img, self.game2image(self.x, self.y, center_x, center_y))\n return self.buf\n\n ",
|
||||
"encoding": "long_xor",
|
||||
"md5": "e9514a2de33051b9c6174d31e35b7584",
|
||||
"msg_type": "code"
|
||||
}
|
||||
{
|
||||
"data": {
|
||||
"id": 107029,
|
||||
"rot": 0,
|
||||
"type": "Flower",
|
||||
"x": 230,
|
||||
"y": 340
|
||||
},
|
||||
"encoding": "xor",
|
||||
"md5": "a80484425bfb3c32a6108ac3d37bd3b8",
|
||||
"msg_type": "update"
|
||||
}
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"rot": 308,
|
||||
"type": "Player",
|
||||
"x": 253.65325300861684,
|
||||
"y": 352.330474276039
|
||||
},
|
||||
"encoding": "ascii",
|
||||
"md5": "caa8fb32ac8426f29d32da8c46b843ad",
|
||||
"msg_type": "update"
|
||||
}
|
||||
```
|
||||
|
||||
Much better. So it's information what shapes to draw and position and rotation updates per object. Maybe try collect the final state for all objects so that we can draw an ending "screenshot"?
|
||||
|
||||
```
|
||||
>> [object, ...][0:13842]> reduce (.[] | select(.msg_type == "update")) as $msg ({}; .[$msg.data.id | tostring] = $msg.data) | repl
|
||||
>>> object> length
|
||||
26
|
||||
>>> object> map({type, x, y}) | first
|
||||
{
|
||||
"type": "Player",
|
||||
"x": 253.65325300861684,
|
||||
"y": 352.330474276039
|
||||
}
|
||||
```
|
||||
|
||||
Here we use `reduce` which is a special expression to "accumulate" something, the syntax is `reduce <generator> as $var (<init>; <update>)`. As generator we select all "update" messages and let each output be named `$msg`, our init value is `{}` an empty object and for each messagee we update the object using the object id found in the message with the data for that message.
|
||||
|
||||
This should be enough to draw something. To make things a bit more manageable there is cleaned up version of all that also has some helper functions to generates a screenshot as a SVG file. It also shows how to use fq as a script interpreter.
|
||||
|
||||
TODO: how to create own functions?
|
||||
|
||||
Have a look at [pcap-challenge-2021-09.jq](pcap-challenge-2021-09.jq).
|
||||
|
||||
To run the script do something like this, assuming you have fq in `PATH`:
|
||||
|
||||
```
|
||||
$ ./pcap-challenge-2021-09.jq pcap-challenge-2021-09.tgz > pcap-challenge-2021-09.svg
|
||||
```
|
||||
|
||||
And it should produce this SVG as output:
|
||||
|
||||
![pcap challenge render](pcap-challenge-2021-09.svg)
|
||||
|
91
doc/guides/fra-pcap-challange-2021-09/pcap-challenge-2021-09.jq
Executable file
91
doc/guides/fra-pcap-challange-2021-09/pcap-challenge-2021-09.jq
Executable file
@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env fq -rf
|
||||
|
||||
# xor input array with key array
|
||||
# key will be repeated to fit length of input
|
||||
def xor_array($key):
|
||||
# [1,2,3] | repeat(7) -> [1,2,3,1,2,3,1]
|
||||
def repeat($len):
|
||||
( length as $l
|
||||
| [.[range($len) % $l]]
|
||||
);
|
||||
( . as $input
|
||||
# [$input, $key repeated]
|
||||
| [ $input
|
||||
, ($key | repeat($input | length))
|
||||
]
|
||||
# [[$input[0], $key[0], ...]
|
||||
| transpose
|
||||
| map(bxor(.[0]; .[1]))
|
||||
);
|
||||
|
||||
( first(.uncompressed.files[] | select(.name == "triangle.pcap"))
|
||||
| .data.tcp_connections[0].server.stream
|
||||
| fromjsonl
|
||||
| map(
|
||||
( if .encoding == "xor" then
|
||||
.data |= (fromhex | explode | xor_array([71])| implode)
|
||||
end
|
||||
| if .encoding == "long_xor" then
|
||||
( .data |=
|
||||
( fromhex
|
||||
| explode
|
||||
| xor_array("GravityForce" | explode)
|
||||
| implode
|
||||
)
|
||||
)
|
||||
end
|
||||
| if .msg_type == "update" then .data |= fromjson end
|
||||
)
|
||||
) as $msgs
|
||||
| { "svg": {
|
||||
# move viewbox to where the objects are
|
||||
"@viewBox": "50 120 350 350",
|
||||
"@width": 350,
|
||||
"@height": 350,
|
||||
"@xmlns": "http://www.w3.org/2000/svg",
|
||||
"rect": [
|
||||
{ "#seq": -1,
|
||||
"@fill": "#101010",
|
||||
"@x": 50,
|
||||
"@y": 120,
|
||||
"@width": 350,
|
||||
"@height": 350
|
||||
}
|
||||
# gather last update for all objects and draw them
|
||||
, ( reduce ($msgs[] | select(.msg_type == "update")) as $msg (
|
||||
{};
|
||||
# use tostring as object keys can only be strings
|
||||
.[$msg.data.id | tostring] = $msg.data
|
||||
)
|
||||
| { Player: {style: "fill: #0000ff", size: 15},
|
||||
Flower: {style: "fill: #00d000", size: 10},
|
||||
Rock: {style: "fill: #a0a0a0", size: 8}
|
||||
} as $types
|
||||
| .[]
|
||||
| $types[.type] as $t
|
||||
| { "@width": $t.size,
|
||||
"@height": $t.size,
|
||||
"@style": $t.style,
|
||||
"@transform": "rotate(\(.rot) \(.x-$t.size/2) \(.y-$t.size/2))",
|
||||
"@x": (.x-$t.size/2),
|
||||
"@y": (.y-$t.size/2)
|
||||
}
|
||||
)
|
||||
],
|
||||
"polyline": {
|
||||
"#seq": 1,
|
||||
"@fill": "none",
|
||||
"@stroke": "#5050d0",
|
||||
"@stroke-dasharray": "5 10",
|
||||
"@points":
|
||||
( [ $msgs[]
|
||||
| select(.msg_type == "update" and .data.type == "Player")
|
||||
| .data.x, .data.y
|
||||
]
|
||||
| join(" ")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
| toxml({indent: 2})
|
||||
)
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 28 KiB |
BIN
doc/guides/fra-pcap-challange-2021-09/pcap-challenge-2021-09.tgz
Normal file
BIN
doc/guides/fra-pcap-challange-2021-09/pcap-challenge-2021-09.tgz
Normal file
Binary file not shown.
245
doc/usage.md
245
doc/usage.md
@ -406,8 +406,6 @@ There is also `tobitsrange` and `tobytesrange` which does the same thing but wil
|
||||
|
||||
Both `.[index]` and `.[start:end]` support negative indices to index from end.
|
||||
|
||||
TODO: tobytesrange, padding
|
||||
|
||||
#### Binary array
|
||||
|
||||
Is an array of numbers, strings, binaries or other nested binary arrays. When used as input to `tobits`/`tobytes` the following rules are used:
|
||||
@ -428,92 +426,175 @@ Some examples:
|
||||
|
||||
`[(.a | tobytes[-10:]), 255, (.b | tobits[:10])] | tobytes` the concatenation of the last 10 bytes of `.a`, a byte with value 255 and the first 10 bits of `.b`.
|
||||
|
||||
The difference between `tobits` and `tobytes` is
|
||||
|
||||
TODO: padding and alignment
|
||||
|
||||
## Functions
|
||||
|
||||
All decode functions are available in two forms, just `<format>` (like `mp3`) that returns a decode value on error and `from_<format>` which throws error on decode error.
|
||||
fq has all the same standard library jq functions and in addition some new ones.
|
||||
|
||||
### Additional generic functions
|
||||
|
||||
#### `grep_by(f)`
|
||||
Recursively select using a filter. Ex: `grep_by(. > 180 and . < 200)`, `first(grep_by(format == "id3v2"))`.
|
||||
|
||||
#### `group`
|
||||
Group values, same as `group_by(.)`.
|
||||
|
||||
#### `streaks`, `streaks_by(f)`
|
||||
Like `group` but groups streaks based on condition.
|
||||
|
||||
#### `count`, `count_by(f)`
|
||||
Like `group` but counts groups lengths based on condition.
|
||||
|
||||
#### `delta`, `delta_by(f)`
|
||||
Array with difference between consecutive. `delta` is same as `delta_by(.b - .a)`.
|
||||
|
||||
#### `chunk($size)`
|
||||
Split array or string into `$size` length chunks. Last chunk might be shorter.
|
||||
|
||||
#### `path_to_expr`
|
||||
Converts a path value `["key", 1]` to a string `".key[1]"`.
|
||||
|
||||
#### `expr_to_path`
|
||||
Converts from a string `".key[1]"` to path value `["key", 1]`.
|
||||
|
||||
#### `diff($a; $b)`
|
||||
Produce a diff between `$a` and `$b`. Differences are represented as a object `{a: <value from a>, b: <value from b>}`.
|
||||
|
||||
#### `band`, `bor`, `bxor`, `bsl`, `bsr`, `bnot`.
|
||||
Bitwise functions. Works the same as jq math functions. Functions with no arguments like `1 | bnot` uses only input, functions with more than one argument ignores input, `bsl(1; 3)`.
|
||||
|
||||
#### `repl`/`repl($opts)`
|
||||
Nested REPL. Must be last in a pipeline. `1 | repl`, can "slurp" outputs. Ex: `1, 2, 3 | repl`, `[1,2,3] | repl({compact: true})`.
|
||||
|
||||
#### `slurp("<name>")`
|
||||
Slurp outputs and save them to `$name`. Must be last in the pipeline. Will be available as a global array `$name`. Ex `1,2,3 | slurp("a")`, `$a[]` same as `spew("a")`.
|
||||
|
||||
#### `spew`/`spew("<name>")`
|
||||
Output previously slurped values.
|
||||
|
||||
#### `spew`
|
||||
Outputs all slurps as an object. `spew("<name>")` outputs one slurp. Ex: `spew("a")`.
|
||||
|
||||
#### `paste`
|
||||
Read string from stdin until ^D. Useful for pasting text. Ex: `paste | from_pem | asn1_ber | repl` read from stdin then decode and start a new sub-REPL with result.
|
||||
|
||||
### Format decode functions
|
||||
|
||||
Format decode functions are available in two forms, just `mp3` or `mp3($opts)` that returns a decode value even on error and `from_mp3` or `from_mp3($opts)` which throws error on decode error.
|
||||
|
||||
The the only general format option currently is `force` to ignore decoder asserts.
|
||||
For example to decode as mp3 and ignore assets do `mp3({force: true})` or `decode("mp3"; {force: true})`. From command line you can either do `fq -d mp3 -o force=true . file.mp3` or `fq -d bytes 'mp3({force: true})' file.mp3`.
|
||||
|
||||
Some formats has own options that can be specificed as part of `$opts` or as `-o name=value`. Too see options for a format do `fq -h mp3` or `help(mp3)` in a REPL. From command line you can either do `fq -d mp3 -o max_sync_seek=100 . file.mp3` or `fq -d bytes 'mp3({max_sync_seek: 100})' file.mp3`.
|
||||
|
||||
#### `decode`, `decode("<format>")`, `decode("<format>"; $opts)`
|
||||
Decode format.
|
||||
|
||||
#### `probe`, `probe($opts)`
|
||||
Probe and decode format.
|
||||
|
||||
#### `<format>`, `<format>($opts)`
|
||||
Same as `decode("<format>")` and `decode("<format>"; $opts)`. Decode as format and return decode value even on decode error.
|
||||
|
||||
#### `from_<format>`, `from_<format>($opts)`
|
||||
Same as `decode("<format>")` and `decode("<format>"; $opts)` decode as format but throw error on decode error.
|
||||
|
||||
Note that jq sometimes uses the notation `name/0`, `name/1` etc in error messages and documentation which means `<function-name>/<arity>`. Same function names with different arity are treated as separate functions, but are usually related in some way in practice.
|
||||
|
||||
### Function added in fq
|
||||
#### `print`, `println`, `printerr`, `printerrln`
|
||||
Print string or if not a string compact JSON value to stdout or stderr.
|
||||
|
||||
- All standard library functions from jq
|
||||
- Adds a few new general functions:
|
||||
- `print`, `println`, `printerr`, `printerrln` prints to stdout and stderr.
|
||||
- `group` group values, same as `group_by(.)`.
|
||||
- `streaks`, `streaks_by(f)` like `group` but groups streaks based on condition.
|
||||
- `count`, `count_by(f)` like `group` but counts groups lengths.
|
||||
- `debug(f)` like `debug` but uses arg to produce a debug message. `{a: 123} | debug({a}) | ...`.
|
||||
- `path_to_expr` from `["key", 1]` to `".key[1]"`.
|
||||
- `expr_to_path` from `".key[1]"` to `["key", 1]`.
|
||||
- `diff($a; $b)` produce diff object between two values.
|
||||
- `delta`, `delta_by(f)`, array with difference between all consecutive pairs.
|
||||
- `chunk(f)`, split array or string into even chunks
|
||||
- Bitwise functions `band`, `bor`, `bxor`, `bsl`, `bsr` and `bnot`. Works the same as jq math functions,
|
||||
unary uses input and if more than one argument all as arguments ignoring the input. Ex: `1 | bnot` `bsl(1; 3)`
|
||||
- Adds some decode value specific functions:
|
||||
- `root` tree root for value
|
||||
- `buffer_root` root value of buffer for value
|
||||
- `format_root` root value of format for value
|
||||
- `parent` parent value
|
||||
- `parents` output parents of value
|
||||
- `topath` path of value. Use `path_to_expr` to get a string representation.
|
||||
- `tovalue`, `tovalue($opts)` symbolic value if available otherwise actual value
|
||||
- `toactual`, `toactual($opts)` actual value (usually the decoded value)
|
||||
- `tosym`, `tosym($opts)` symbolic value (mapped etc)
|
||||
- `todescription` description of value
|
||||
- `torepr` converts decode value into what it represents. For example convert msgpack decode value
|
||||
into a value representing its JSON representation.
|
||||
- All regexp functions work with binary as input and pattern argument with these differences
|
||||
compared to when using string input:
|
||||
- All offset and length will be in bytes.
|
||||
- For `capture` the `.string` value is a binary.
|
||||
- If pattern is a binary it will be matched literally and not as a regexp.
|
||||
- If pattern is a binary or flags include "b" each input byte will be read as separate code points
|
||||
- String functions are not overloaded to support binary for now as some of them might have behaviors that might be confusing.
|
||||
- `explode` is overloaded to work with binary. Will explode into array of the unit of the binary.
|
||||
end of binary.
|
||||
instead of possibly multi-byte UTF-8 codepoints. This allows to match raw bytes. Ex: `match("\u00ff"; "b")`
|
||||
will match the byte `0xff` and not the UTF-8 encoded codepoint for 255, `match("[^\u00ff]"; "b")` will match
|
||||
all non-`0xff` bytes.
|
||||
- `grep` functions take 1 or 2 arguments. First is a scalar to match, where a string is
|
||||
treated as a regexp. A binary will match exact bytes. Second argument are regexp
|
||||
flags with addition that "b" will treat each byte in the input binary as a code point, this
|
||||
makes it possible to match exact bytes.
|
||||
- `grep($v)`, `grep($v; $flags)` recursively match value and binary
|
||||
- `vgrep($v)`, `vgrep($v; $flags)` recursively match value
|
||||
- `bgrep($v)`, `bgrep($v; $flags)` recursively match binary
|
||||
- `fgrep($v)`, `fgrep($v; $flags)` recursively match field name
|
||||
- `grep_by(f)` recursively match using a filter. Ex: `grep_by(. > 180 and . < 200)`, `first(grep_by(format == "id3v2"))`.
|
||||
- Binary:
|
||||
- `tobits` - Transform input to binary with bit as unit, does not preserve source range, will start at zero.
|
||||
- `tobitsrange` - Transform input to binary with bit as unit, preserves source range if possible.
|
||||
- `tobytes` - Transform input to binary with byte as unit, does not preserve source range, will start at zero.
|
||||
- `tobytesrange` - Transform input binary with byte as unit, preserves source range if possible.
|
||||
- `.[start:end]`, `.[:end]`, `.[start:]` - Slice binary from start to end preserve source range.
|
||||
- `open` open file for reading
|
||||
- All decode functions take an optional option argument. The only option currently is `force` to ignore decoder asserts.
|
||||
For example to decode as mp3 and ignore assets do `mp3({force: true})` or `decode("mp3"; {force: true})`, from command line
|
||||
you currently have to do `fq -d bytes 'mp3({force: true})' file`.
|
||||
- `decode`, `decode("<format>")`, `decode("<format>"; $opts)` decode format
|
||||
- `probe`, `probe($opts)` probe and decode format
|
||||
- `mp3`, `mp3($opts)`, ..., `<format>`, `<format>($opts)` same as `decode("<format>")`, `decode("<format>"; $opts)` decode as format and return decode value even on decode error.
|
||||
- `from_mp3`, `from_mp3($opts)`, ..., `from_<format>`, `from_<format>($opts)` same as `decode("<format>")`, `decode("<format>"; $opts)` decode as format but throw error on decode error.
|
||||
- Display shows hexdump/ASCII/tree for decode values and jq value for other types.
|
||||
- `d`/`d($opts)` display value and truncate long arrays and binaries
|
||||
- `da`/`da($opts)` display value and don't truncate arrays
|
||||
- `dd`/`dd($opts)` display value and don't truncate arrays or binaries
|
||||
- `dv`/`dv($opts)` verbosely display value and don't truncate arrays but truncate binaries
|
||||
- `ddv`/`ddv($opts)` verbosely display value and don't truncate arrays or binaries
|
||||
- `hd`/`hexdump` hexdump value
|
||||
- `repl`/`repl($opts)` nested REPL, must be last in a pipeline. `1 | repl`, can "slurp" outputs. Ex: `1, 2, 3 | repl`, `[1,2,3] | repl({compact: true})`.
|
||||
- `slurp("<name>")` slurp outputs and save them to `$name`, must be last in the pipeline. Will be available as a global array `$name`. Ex `1,2,3 | slurp("a")`, `$a[]` same as `spew("a")`.
|
||||
- `spew`/`spew("<name>")` output previously slurped values. `spew` outputs all slurps as an object, `spew("<name>")` outputs one slurp. Ex: `spew("a")`.
|
||||
- `paste` read string from stdin until ^D. Useful for pasting text.
|
||||
- Ex: `paste | from_pem | asn1_ber | repl` read from stdin then decode and start a new sub-REPL with result.
|
||||
#### `root`
|
||||
Root decode value for decode value.
|
||||
|
||||
#### `buffer_root`
|
||||
Root decode value of buffer for decode value.
|
||||
|
||||
#### `format_root`
|
||||
Root decode value of format for decode value.
|
||||
|
||||
#### `parent`
|
||||
Parent decode value for decode value.
|
||||
|
||||
#### `parents`
|
||||
Outputs all parent decode values from decode value.
|
||||
|
||||
#### `topath`
|
||||
Path for decode value. Use `path_to_expr` to get a string representation.
|
||||
|
||||
#### `tovalue`, `tovalue($opts)`
|
||||
Symbolic, if available, or actual value for decode value.
|
||||
|
||||
#### `toactual`, `toactual($opts)`
|
||||
Actual value for decode value.
|
||||
|
||||
#### `tosym`, `tosym($opts)`
|
||||
Symbolic value for decode value.
|
||||
|
||||
#### `todescription`
|
||||
Description for decode value.
|
||||
|
||||
#### `torepr`
|
||||
Converts decode value into what it represents. For example converts msgpack decode value into a value representing its JSON representation.
|
||||
|
||||
### Display functions
|
||||
|
||||
Display shows hexdump, ASCII and tree column dump for decode values and jq value for other types.
|
||||
|
||||
#### `d`/`d($opts)`
|
||||
display value and truncate long arrays and binaries.
|
||||
|
||||
#### `da`/`da($opts)`
|
||||
Display value and don't truncate arrays.
|
||||
|
||||
#### `dd`/`dd($opts)`
|
||||
Display value and don't truncate arrays or binaries.
|
||||
|
||||
#### `dv`/`dv($opts)`
|
||||
Verbosely display value and don't truncate arrays but truncate binaries.
|
||||
|
||||
#### `ddv`/`ddv($opts)`
|
||||
Verbosely display value and don't truncate arrays or binaries.
|
||||
|
||||
#### `hd`/`hexdump`
|
||||
Hexdump value.
|
||||
|
||||
### Binary values
|
||||
|
||||
Binary values represents raw bits or bytes. When used in standard jq expressions they will behave as strings (UTF-8) with some exceptions listed below.
|
||||
|
||||
- All regexp functions work with binary as input and pattern argument with these differences
|
||||
compared to when using string input:
|
||||
- All offset and length will be in bytes.
|
||||
- For `capture` the `.string` value is a binary.
|
||||
- If pattern is a binary it will be matched literally and not as a regexp.
|
||||
- If pattern is a binary or flags include "b" each input byte will be read as separate code points
|
||||
- `explode` is overloaded to work with binary. Will explode into array of the unit of the binary.
|
||||
- `.[start:end]`, `.[:end]`, `.[start:]` - Slice binary from start to end preserve source range.
|
||||
|
||||
#### `grep($v)`, `grep($v; $flags)`, `vgrep($v)`, `vgrep($v; $flags)`, `bgrep($v)`, `bgrep($v; $flags)`
|
||||
Recursively match `$v`.
|
||||
|
||||
`$v` is a scalar to match, where a string is treated as a regexp. A binary will match exact bytes.
|
||||
`$flags` argument are regexp flags with additional flag "b" that will treat each byte in the input binary
|
||||
as a code point. This makes it possible to match exact bytes.
|
||||
|
||||
#### `fgrep($v)`, `fgrep($v; $flags)`
|
||||
Recursively match field name in for decode value.
|
||||
|
||||
#### `tobits`
|
||||
Transform input to binary with bit as unit and don't preserve source range.
|
||||
|
||||
#### `tobitsrange`
|
||||
Transform input to binary with bit as unit and preserve source range.
|
||||
|
||||
#### `tobytes`
|
||||
Transform input to binary with byte as unit and don't preserve source range.
|
||||
|
||||
#### `tobytesrange`
|
||||
Transform input to binary with byte as unit and preserve source range.
|
||||
|
||||
#### `open`
|
||||
Open file for reading.
|
||||
|
||||
### Naming inconsistencies
|
||||
|
||||
|
12
go.mod
12
go.mod
@ -9,7 +9,7 @@ require (
|
||||
// bump: gomod-BurntSushi/toml /github\.com\/BurntSushi\/toml v(.*)/ https://github.com/BurntSushi/toml.git|^1
|
||||
// bump: gomod-BurntSushi/toml command go get -d github.com/BurntSushi/toml@v$LATEST && go mod tidy
|
||||
// bump: gomod-BurntSushi/toml link "Source diff $CURRENT..$LATEST" https://github.com/BurntSushi/toml/compare/v$CURRENT..v$LATEST
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
|
||||
// bump: gomod-creasty-defaults /github\.com\/creasty\/defaults v(.*)/ https://github.com/creasty/defaults.git|^1
|
||||
// bump: gomod-creasty-defaults command go get -d github.com/creasty/defaults@v$LATEST && go mod tidy
|
||||
@ -48,7 +48,7 @@ require (
|
||||
// bump: gomod-golang-x-crypto /golang\.org\/x\/crypto v(.*)/ https://github.com/golang/crypto.git|^0
|
||||
// bump: gomod-golang-x-crypto command go get -d golang.org/x/crypto@v$LATEST && go mod tidy
|
||||
// bump: gomod-golang-x-crypto link "Tags" https://github.com/golang/crypto/tags
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/crypto v0.24.0
|
||||
|
||||
// has no tags
|
||||
// go get -d golang.org/x/exp@master && go mod tidy
|
||||
@ -57,17 +57,17 @@ require (
|
||||
// bump: gomod-golang-x-net /golang\.org\/x\/net v(.*)/ https://github.com/golang/net.git|^0
|
||||
// bump: gomod-golang-x-net command go get -d golang.org/x/net@v$LATEST && go mod tidy
|
||||
// bump: gomod-golang-x-net link "Tags" https://github.com/golang/net/tags
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/net v0.26.0
|
||||
|
||||
// bump: gomod-golang-x-term /golang\.org\/x\/term v(.*)/ https://github.com/golang/term.git|^0
|
||||
// bump: gomod-golang-x-term command go get -d golang.org/x/term@v$LATEST && go mod tidy
|
||||
// bump: gomod-golang-x-term link "Tags" https://github.com/golang/term/tags
|
||||
golang.org/x/term v0.20.0
|
||||
golang.org/x/term v0.21.0
|
||||
|
||||
// bump: gomod-golang/text /golang\.org\/x\/text v(.*)/ https://github.com/golang/text.git|^0
|
||||
// bump: gomod-golang/text command go get -d golang.org/x/text@v$LATEST && go mod tidy
|
||||
// bump: gomod-golang/text link "Source diff $CURRENT..$LATEST" https://github.com/golang/text/compare/v$CURRENT..v$LATEST
|
||||
golang.org/x/text v0.15.0
|
||||
golang.org/x/text v0.16.0
|
||||
|
||||
// bump: gomod-gopkg.in/yaml.v3 /gopkg\.in\/yaml\.v3 v(.*)/ https://github.com/go-yaml/yaml.git|^3
|
||||
// bump: gomod-gopkg.in/yaml.v3 command go get -d gopkg.in/yaml.v3@v$LATEST && go mod tidy
|
||||
@ -79,6 +79,6 @@ require (
|
||||
github.com/itchyny/timefmt-go v0.1.5 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
)
|
||||
|
24
go.sum
24
go.sum
@ -1,5 +1,5 @@
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
|
||||
github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||
github.com/ergochat/readline v0.1.1 h1:C8Uuo3ybB23GWOt0uxmHbGzKM9owmtXary6Clrj84s0=
|
||||
@ -25,18 +25,18 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/wader/gojq v0.12.1-0.20240401131232-6c6bc364201a h1:P881Oecjt9FEXrwkGJ6UObJksxejJaF/fKq1ZfXpiVE=
|
||||
github.com/wader/gojq v0.12.1-0.20240401131232-6c6bc364201a/go.mod h1:qVrzkUdnBtJvM4twyRQ6xdziPSnSp35dLm4s/DN2iP4=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
Loading…
Reference in New Issue
Block a user