2
0
mirror of https://github.com/gchq/CyberChef synced 2025-12-05 23:53:27 +00:00

Compare commits

...

83 Commits

Author SHA1 Message Date
n1474335
ab55b91da1 7.7.0 2018-02-13 15:51:45 +00:00
n1474335
59f7774964 Merge branch 'feature-xkcd-rand' 2018-02-13 15:51:02 +00:00
n1474335
b5eb44af9f When highlighting operation descriptions in search results, HTML tags are now ignored. 2018-02-13 15:43:55 +00:00
n1474335
8518fa67f2 Added 'XKCD Random Number' operation 2018-02-13 15:05:55 +00:00
n1474335
f6b68f9880 Increased file overlay slice to 4096 for larger screen support 2018-02-12 13:57:01 +00:00
n1474335
f6b2783f8b File overlays now show a blurred representation of the file in the background. 2018-02-09 16:26:39 +00:00
n1474335
078849041f 7.6.3 2018-01-26 19:14:15 +00:00
n1474335
1c711f5e03 Updated dependencies 2018-01-26 19:14:05 +00:00
n1474335
614af0602a 7.6.2 2018-01-25 18:41:53 +00:00
n1474335
e55cfe0bc1 Fixed 'Syntax highlighter' operation. Using highlight.js instead of google-code-prettify. 2018-01-25 18:41:47 +00:00
n1474335
2b703b2b9b HTML outputs are now unescaped correctly when converted to a string 2018-01-25 16:25:19 +00:00
n1474335
170feaaff2 7.6.1 2018-01-25 14:03:19 +00:00
n1474335
870c2b6d8b Fixed deep copy bug with Fork/Register ingredient values. 2018-01-25 14:03:13 +00:00
n1474335
eee8b7db56 Fixed dispatchEvent call in recipe loading chain. 2018-01-25 13:46:06 +00:00
n1474335
3c669a075e 7.6.0 2018-01-25 13:45:05 +00:00
n1474335
f528930ad2 Added 'Sleep' operation. 2018-01-25 13:44:39 +00:00
n1474335
231322eddf 7.5.6 2018-01-24 16:54:42 +00:00
n1474335
8e6763c165 'Register' and 'Fork' now play well together. 2018-01-24 16:54:37 +00:00
n1474335
f091918575 7.5.5 2018-01-24 15:50:10 +00:00
n1474335
bb077c87b3 'Extract file paths' operation now handles 8.3 windows file paths correctly. 2018-01-24 15:50:05 +00:00
n1474335
fe8f8bc712 Setting a text value in the input now closes any open files. 2018-01-22 19:58:21 +00:00
n1474335
abe87830cd Operation tooltips now disappear if you hover over them while dragging an opertion. 2018-01-22 17:51:04 +00:00
n1474335
7490651a06 7.5.4 2018-01-22 17:10:02 +00:00
n1474335
6220128a74 Fixed delimiter options in StrUtils. Closes #238. 2018-01-22 17:09:58 +00:00
n1474335
ec205f4f7d 7.5.3 2018-01-18 19:53:01 +00:00
n1474335
512487328d Fixed bugs in pretty recipe format generation 2018-01-18 18:35:17 +00:00
n1474335
6fbb2f26d1 7.5.2 2018-01-18 15:27:26 +00:00
n1474335
c91bfeaa81 Merge branch 'qistoph-SplitAndJoin' 2018-01-18 15:26:34 +00:00
n1474335
aa2b3b2843 Changed order of split delimiters, placing comma first. 2018-01-18 15:26:09 +00:00
Chris van Marle
90d8be48d4 Make Split more flexible so it can be used to join 2018-01-17 15:52:25 +01:00
n1474335
aa6890432c 7.5.1 2018-01-12 23:59:03 +00:00
n1474335
192d0ed8a6 Merge branch 'feature-unicode-strings' 2018-01-12 23:57:20 +00:00
n1474335
fff188eb30 Merged master into feature-unicode-strings 2018-01-12 23:57:02 +00:00
n1474335
71067939e3 Added Regex tests and updated description 2018-01-12 23:51:51 +00:00
n1474335
b07c014b48 Added more modifiers to the Regex operation 2018-01-12 23:42:48 +00:00
n1474335
f2c073798b 'Strings' now supports various different match types in ASCII and Unicode 2018-01-12 23:09:27 +00:00
n1474335
4cc38db895 Added documentation. 2018-01-12 22:14:06 +00:00
n1474335
2762319dbb 7.5.0 2018-01-12 18:29:10 +00:00
n1474335
8e5d43dfa8 Merge branch 'feature-hamming' 2018-01-12 18:28:55 +00:00
n1474335
82ad8cc444 BigNumber Dish types are now converted to fixed notation instead of exponential when translated between types 2018-01-12 18:28:40 +00:00
n1474335
1d6bf39548 Added 'Hamming Distance' operation. 2018-01-12 18:17:28 +00:00
n1474335
ec02b7deda Regexes are now checked for 0-length matches and incremented manually to avoid infinite loops 2018-01-10 19:44:25 +00:00
n1474335
fa938f832f Fixed IE 11 detection so that support message is correctly shown. 2018-01-10 18:38:17 +00:00
n1474335
6f59d9217c Changed 'Number' option to 'Integer' in PRNG op 2018-01-06 17:01:01 +00:00
n1474335
7176e5ca6e 7.4.0 2018-01-06 16:34:06 +00:00
n1474335
429829471f Merge branch 'feature-paste-performance' 2018-01-06 16:33:12 +00:00
n1474335
4760e539b7 PRNG operation now supports BigNumbers as output 2018-01-06 16:30:17 +00:00
n1474335
f53e7ad617 Chef now defaults to treat as UTF8 if option is not set 2018-01-06 16:21:42 +00:00
n1474335
28c83fa921 Dish translation now obeys UTF8 rules 2018-01-06 16:02:50 +00:00
n1474335
4588cd151c Data pasted into the input is treat as a file if it's over the IO threshold 2018-01-06 15:29:58 +00:00
n1474335
2d9f87abef Added more loading messages 2018-01-05 20:26:51 +00:00
n1474335
491e6f5f5f 7.3.0 2018-01-05 18:50:48 +00:00
n1474335
ab7c05284d Merge branch 'artemisbot-features/big-number' 2018-01-05 18:50:13 +00:00
n1474335
0586fa0e01 Fixed failing Fork test that required an error. 2018-01-05 18:49:51 +00:00
n1474335
53eba2337c BCD operations now support BigNumbers 2018-01-05 18:38:23 +00:00
n1474335
283d3e1e7b Blank BigNumber dishes are now treat as NaN instead of erroring 2018-01-05 18:20:06 +00:00
n1474335
7992a540ae Conversion operations now support BigNumbers 2018-01-05 18:14:03 +00:00
n1474335
3f3e7a78eb Arithmetic operations now support BigNumbers 2018-01-05 18:04:55 +00:00
n1474335
8d3d39acd3 Merge branch 'features/big-number' of https://github.com/artemisbot/CyberChef into artemisbot-features/big-number 2018-01-05 17:31:27 +00:00
n1474335
7b20aba2ff Improved descriptions for 'Unescape string' and 'Escape string' operations 2018-01-04 18:32:03 +00:00
n1474335
bbfb732d8f 7.2.3 2018-01-04 17:48:08 +00:00
n1474335
566adbcda5 'Unescape string' operation now works with capitalised hex 2018-01-04 17:48:01 +00:00
n1474335
1bff490fa4 7.2.2 2018-01-04 14:43:54 +00:00
n1474335
d38375a08c Improved error handling for file uploads 2018-01-04 14:43:49 +00:00
n1474335
56551712d6 Began implementing UTF-16 support in the 'Strings' operation. 2018-01-03 16:51:10 +00:00
Matt C
c241d2f90b Adds basic BigNumber type support
Fixes `To Base` & `From Base` issues as reported on twitter
2018-01-03 11:26:31 +00:00
n1474335
0ba28dc891 Updated README.md 2018-01-02 16:02:46 +00:00
n1474335
ac9af6d2ba 7.2.1 2018-01-02 15:33:08 +00:00
n1474335
90d9e087f7 'Take bytes' and 'Drop bytes' operations now support ArrayBuffers 2018-01-02 15:33:02 +00:00
n1474335
50b24d9a56 Fixed no-trailing-space lint 2018-01-02 14:46:35 +00:00
n1474335
10f42e9a7f 7.2.0 2018-01-02 00:13:34 +00:00
n1474335
987bd303a0 Merge branch 'forge' 2018-01-02 00:13:20 +00:00
n1474335
a3f58fb831 Added 'Pseudo-Random Number Generator' operation. 2018-01-01 20:50:01 +00:00
n1474335
f52f5a0edb Added 'RC2 Encrypt' and 'RC2 Decrypt' operations. 2018-01-01 19:50:06 +00:00
n1474335
b58942f69a Rewrote PBKDF2 operation to use Forge instead of CryptoJS and improved the API for both PBKDF2 and EVP operations. 2018-01-01 18:49:22 +00:00
n1474335
68e52d1645 Removed CryptoJS encodings from Blowfish operations. 2018-01-01 17:54:45 +00:00
n1474335
9fc7e6cd98 Rewrote AES, DES and Triple DES operations to use Forge instead of CryptoJS, simplifying their options and adding many tests. Removed Rabbit operations. Fixes #63 and #210. 2018-01-01 16:09:58 +00:00
n1474335
87f346d88c 7.1.0 2017-12-29 17:48:22 +00:00
n1474335
e423ff2639 Merge branch 'feature-logging' 2017-12-29 17:48:14 +00:00
n1474335
fa6905ef00 Added more comprehensive logging to FlowControl ops and added '>' prefix to all ChefWorker logs to improve clarity 2017-12-29 17:32:23 +00:00
n1474335
8684bc0158 Removed duplicate logging message 2017-12-28 18:33:59 +00:00
n1474335
a96eb450de Improved Recipe logging 2017-12-28 18:24:29 +00:00
n1474335
d079420d46 Added logging with configurable levels to make debugging easier. 2017-12-28 18:17:38 +00:00
53 changed files with 5090 additions and 1496 deletions

View File

@@ -35,7 +35,6 @@
}],
// disable rules from base configurations
"no-console": "off",
"no-control-regex": "off",
// stylistic conventions
@@ -52,6 +51,7 @@
"mode": "minimum"
}],
"indent": ["error", 4, {
"ignoreComments": true,
"ArrayExpression": "first",
"SwitchCase": 1
}],
@@ -90,6 +90,7 @@
"$": false,
"jQuery": false,
"moment": false,
"log": false,
"COMPILE_TIME": false,
"COMPILE_MSG": false,

View File

@@ -186,7 +186,10 @@ module.exports = function (grunt) {
options: webpackConfig,
metaConf: {
target: "node",
entry: "./src/core/config/OperationConfig.js",
entry: [
"babel-polyfill",
"./src/core/config/OperationConfig.js"
],
output: {
filename: "MetaConfig.js",
path: __dirname + "/src/core/config/",
@@ -198,7 +201,10 @@ module.exports = function (grunt) {
},
metaConfDev: {
target: "node",
entry: "./src/core/config/OperationConfig.js",
entry: [
"babel-polyfill",
"./src/core/config/OperationConfig.js"
],
output: {
filename: "MetaConfig.js",
path: __dirname + "/src/core/config/",

View File

@@ -27,7 +27,7 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur
There are four main areas in CyberChef:
1. The **input** box in the top right, where you can paste, type or drag the data you want to operate on.
1. The **input** box in the top right, where you can paste, type or drag the text or file you want to operate on.
2. The **output** box in the bottom right, where the outcome of your processing will be displayed.
3. The **operations** list on the far left, where you can find all the operations that CyberChef is capable of in categorised lists, or by searching.
4. The **recipe** area in the middle, where you can drag the operations that you want to use and specify arguments and options.
@@ -42,32 +42,32 @@ You can use as many operations as you like in simple or complex ways. Some examp
- [Display multiple timestamps as full dates][7]
- [Carry out different operations on data of different types][8]
- [Use parts of the input as arguments to operations][9]
- [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10]
## Features
- Drag and drop
- Operations can be dragged in and out of the recipe list, or reorganised.
- Files can be dragged over the input box to load them directly.
- Files can be dragged over the input box to load them directly into the browser.
- Auto Bake
- Whenever you modify the input or the recipe, CyberChef will automatically bake for you and produce the output immediately.
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
- If any bake takes longer than 200 milliseconds, auto bake will be switched off automatically to prevent further performance issues.
- Breakpoints
- You can set breakpoints on any operation in your recipe to pause execution before running it.
- You can also step through the recipe one operation at a time to see what the data looks like at each stage.
- Save and load recipes
- If you come up with an awesome recipe that you know youll want to use again, just click save and add it to your local storage. It'll be waiting for you next time you visit CyberChef.
- You can also copy a URL which includes your recipe and input which can be shared with others.
- If you come up with an awesome recipe that you know youll want to use again, just click "Save recipe" and add it to your local storage. It'll be waiting for you next time you visit CyberChef.
- You can also copy the URL, which includes your recipe and input, to easily share it with others.
- Search
- If you know the name of the operation you want or a word associated with it, start typing it into the search field and any matching operations will immediately be shown.
- Highlighting
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][10]).
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]).
- Save to file and load from file
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 500MB are supported (depending on your browser), however some operations may take a very long time to run over this much data.
- CyberChef is entirely client-side
- It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
- Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your desktop.
- Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your local machine.
## Browser support
@@ -100,6 +100,7 @@ CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/lice
[5]: https://gchq.github.io/CyberChef/#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw
[6]: https://gchq.github.io/CyberChef/#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ
[7]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA
[8]: https://gchq.github.ioeCyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA
[8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA
[9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ
[10]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4
[10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg
[11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4

2161
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "7.0.0",
"version": "7.7.0",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",
@@ -33,7 +33,8 @@
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1",
"css-loader": "^0.28.7",
"css-loader": "^0.28.9",
"eslint": "^4.17.0",
"exports-loader": "^0.6.4",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6",
@@ -46,7 +47,7 @@
"grunt-eslint": "^20.1.0",
"grunt-exec": "~3.0.0",
"grunt-execute": "^0.2.2",
"grunt-jsdoc": "^2.2.0",
"grunt-jsdoc": "^2.2.1",
"grunt-webpack": "^3.0.2",
"html-webpack-plugin": "^2.30.1",
"imports-loader": "^0.7.1",
@@ -56,18 +57,19 @@
"less-loader": "^4.0.5",
"postcss-css-variables": "^0.8.0",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.9",
"postcss-loader": "^2.0.10",
"style-loader": "^0.19.1",
"url-loader": "^0.6.2",
"val-loader": "^1.1.0",
"web-resource-inliner": "^4.2.0",
"web-resource-inliner": "^4.2.1",
"webpack": "^3.10.0",
"webpack-dev-server": "^2.9.7",
"webpack-dev-server": "^2.11.1",
"webpack-node-externals": "^1.6.0",
"worker-loader": "^1.1.0"
},
"dependencies": {
"babel-polyfill": "^6.26.0",
"bignumber.js": "^6.0.0",
"bootstrap": "^3.3.7",
"bootstrap-colorpicker": "^2.5.2",
"bootstrap-switch": "^3.3.4",
@@ -79,16 +81,19 @@
"esprima": "^4.0.0",
"exif-parser": "^0.1.12",
"file-saver": "^1.3.3",
"google-code-prettify": "^1.0.5",
"jquery": "^3.2.1",
"highlight.js": "^9.12.0",
"jquery": "^3.3.1",
"js-crc": "^0.2.0",
"js-sha3": "^0.7.0",
"jsbn": "^1.1.0",
"jsonpath": "^1.0.0",
"jsrsasign": "8.0.4",
"lodash": "^4.17.4",
"loglevel": "^1.6.1",
"loglevel-message-prefix": "^3.0.0",
"moment": "^2.20.1",
"moment-timezone": "^0.5.14",
"node-forge": "^0.7.1",
"node-md6": "^0.1.0",
"nwmatcher": "^1.4.3",
"otp": "^0.1.3",
@@ -99,6 +104,7 @@
"vkbeautify": "^0.99.3",
"xmldom": "^0.1.27",
"xpath": "0.0.27",
"xregexp": "^4.0.0",
"zlibjs": "^0.3.1"
},
"scripts": {

View File

@@ -34,10 +34,12 @@ const Chef = function() {
* @returns {number} response.error - The error object thrown by a failed operation (false if no error)
*/
Chef.prototype.bake = async function(input, recipeConfig, options, progress, step) {
let startTime = new Date().getTime(),
log.debug("Chef baking");
const startTime = new Date().getTime(),
recipe = new Recipe(recipeConfig),
containsFc = recipe.containsFlowControl(),
error = false;
notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8;
let error = false;
if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
@@ -69,7 +71,7 @@ Chef.prototype.bake = async function(input, recipeConfig, options, progress, ste
try {
progress = await recipe.execute(this.dish, progress);
} catch (err) {
console.log(err);
log.error(err);
error = {
displayStr: err.displayStr,
};
@@ -79,13 +81,13 @@ Chef.prototype.bake = async function(input, recipeConfig, options, progress, ste
// Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
// This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
// The threshold is specified in KiB.
const threshold = (options.outputFileThreshold || 1024) * 1024;
const threshold = (options.ioDisplayThreshold || 1024) * 1024;
const returnType = this.dish.size() > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
return {
result: this.dish.type === Dish.HTML ?
this.dish.get(Dish.HTML) :
this.dish.get(returnType),
this.dish.get(Dish.HTML, notUTF8) :
this.dish.get(returnType, notUTF8),
type: Dish.enumLookup(this.dish.type),
progress: progress,
duration: new Date().getTime() - startTime,
@@ -112,6 +114,8 @@ Chef.prototype.bake = async function(input, recipeConfig, options, progress, ste
* @returns {number} The time it took to run the silent bake in milliseconds.
*/
Chef.prototype.silentBake = function(recipeConfig) {
log.debug("Running silent bake");
let startTime = new Date().getTime(),
recipe = new Recipe(recipeConfig),
dish = new Dish("", Dish.STRING);

View File

@@ -11,6 +11,15 @@ import Chef from "./Chef.js";
import OperationConfig from "./config/MetaConfig.js";
import OpModules from "./config/modules/Default.js";
// Add ">" to the start of all log messages in the Chef Worker
import loglevelMessagePrefix from "loglevel-message-prefix";
loglevelMessagePrefix(log, {
prefixes: [],
staticPrefixes: [">"],
prefixFormat: "%p"
});
// Set up Chef instance
self.chef = new Chef();
@@ -42,6 +51,8 @@ self.postMessage({
self.addEventListener("message", function(e) {
// Handle message
const r = e.data;
log.debug("ChefWorker receiving command '" + r.action + "'");
switch (r.action) {
case "bake":
bake(r.data);
@@ -61,6 +72,9 @@ self.addEventListener("message", function(e) {
r.data.pos
);
break;
case "setLogLevel":
log.setLevel(r.data, false);
break;
default:
break;
}
@@ -86,7 +100,7 @@ async function bake(data) {
);
self.postMessage({
action: "bakeSuccess",
action: "bakeComplete",
data: response
});
} catch (err) {
@@ -121,7 +135,7 @@ function loadRequiredModules(recipeConfig) {
let module = self.OperationConfig[op.op].module;
if (!OpModules.hasOwnProperty(module)) {
console.log("Loading module " + module);
log.info("Loading module " + module);
self.sendStatusMessage("Loading module " + module);
self.importScripts(self.docURL + "/" + module + ".js");
}

View File

@@ -1,14 +1,16 @@
import Utils from "./Utils.js";
import BigNumber from "bignumber.js";
/**
* The data being operated on by each operation.
*
* @author n1474335 [n1474335@gmail.com]
* @author Matt C [matt@artemisbot.uk]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*
* @class
* @param {byteArray|string|number|ArrayBuffer} value - The value of the input data.
* @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data.
* @param {number} type - The data type of value, see Dish enums.
*/
const Dish = function(value, type) {
@@ -47,6 +49,12 @@ Dish.HTML = 3;
* @enum
*/
Dish.ARRAY_BUFFER = 4;
/**
* Dish data type enum for BigNumbers.
* @readonly
* @enum
*/
Dish.BIG_NUMBER = 5;
/**
@@ -57,22 +65,22 @@ Dish.ARRAY_BUFFER = 4;
* @returns {number} The data type enum value.
*/
Dish.typeEnum = function(typeStr) {
switch (typeStr) {
case "byteArray":
case "Byte array":
switch (typeStr.toLowerCase()) {
case "bytearray":
case "byte array":
return Dish.BYTE_ARRAY;
case "string":
case "String":
return Dish.STRING;
case "number":
case "Number":
return Dish.NUMBER;
case "html":
case "HTML":
return Dish.HTML;
case "arrayBuffer":
case "ArrayBuffer":
case "arraybuffer":
case "array buffer":
return Dish.ARRAY_BUFFER;
case "bignumber":
case "big number":
return Dish.BIG_NUMBER;
default:
throw "Invalid data type string. No matching enum.";
}
@@ -83,8 +91,8 @@ Dish.typeEnum = function(typeStr) {
* Returns the data type string for the given type enum.
*
* @static
* @param {string} typeEnum - The enum value of the data type.
* @returns {number} The data type as a string.
* @param {number} typeEnum - The enum value of the data type.
* @returns {string} The data type as a string.
*/
Dish.enumLookup = function(typeEnum) {
switch (typeEnum) {
@@ -98,6 +106,8 @@ Dish.enumLookup = function(typeEnum) {
return "html";
case Dish.ARRAY_BUFFER:
return "ArrayBuffer";
case Dish.BIG_NUMBER:
return "BigNumber";
default:
throw "Invalid data type enum. No matching type.";
}
@@ -107,10 +117,11 @@ Dish.enumLookup = function(typeEnum) {
/**
* Sets the data value and type and then validates them.
*
* @param {byteArray|string|number|ArrayBuffer} value - The value of the input data.
* @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data.
* @param {number} type - The data type of value, see Dish enums.
*/
Dish.prototype.set = function(value, type) {
log.debug("Dish type: " + Dish.enumLookup(type));
this.value = value;
this.type = type;
@@ -125,11 +136,12 @@ Dish.prototype.set = function(value, type) {
* Returns the value of the data in the type format specified.
*
* @param {number} type - The data type of value, see Dish enums.
* @returns {byteArray|string|number|ArrayBuffer} The value of the output data.
* @param {boolean} [notUTF8] - Do not treat strings as UTF8.
* @returns {byteArray|string|number|ArrayBuffer|BigNumber} The value of the output data.
*/
Dish.prototype.get = function(type) {
Dish.prototype.get = function(type, notUTF8) {
if (this.type !== type) {
this.translate(type);
this.translate(type, notUTF8);
}
return this.value;
};
@@ -139,8 +151,12 @@ Dish.prototype.get = function(type) {
* Translates the data to the given type format.
*
* @param {number} toType - The data type of value, see Dish enums.
* @param {boolean} [notUTF8] - Do not treat strings as UTF8.
*/
Dish.prototype.translate = function(toType) {
Dish.prototype.translate = function(toType, notUTF8) {
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8;
// Convert data to intermediate byteArray type
switch (this.type) {
case Dish.STRING:
@@ -156,6 +172,9 @@ Dish.prototype.translate = function(toType) {
// Array.from() would be nicer here, but it's slightly slower
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
break;
case Dish.BIG_NUMBER:
this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : [];
break;
default:
break;
}
@@ -166,17 +185,25 @@ Dish.prototype.translate = function(toType) {
switch (toType) {
case Dish.STRING:
case Dish.HTML:
this.value = this.value ? Utils.byteArrayToUtf8(this.value) : "";
this.value = this.value ? byteArrayToStr(this.value) : "";
this.type = Dish.STRING;
break;
case Dish.NUMBER:
this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0;
this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0;
this.type = Dish.NUMBER;
break;
case Dish.ARRAY_BUFFER:
this.value = new Uint8Array(this.value).buffer;
this.type = Dish.ARRAY_BUFFER;
break;
case Dish.BIG_NUMBER:
try {
this.value = new BigNumber(byteArrayToStr(this.value));
} catch (err) {
this.value = new BigNumber(NaN);
}
this.type = Dish.BIG_NUMBER;
break;
default:
break;
}
@@ -212,6 +239,8 @@ Dish.prototype.valid = function() {
return typeof this.value === "number";
case Dish.ARRAY_BUFFER:
return this.value instanceof ArrayBuffer;
case Dish.BIG_NUMBER:
return this.value instanceof BigNumber;
default:
return false;
}
@@ -232,6 +261,7 @@ Dish.prototype.size = function() {
case Dish.HTML:
return this.value.length;
case Dish.NUMBER:
case Dish.BIG_NUMBER:
return this.value.toString().length;
case Dish.ARRAY_BUFFER:
return this.value.byteLength;

View File

@@ -52,13 +52,25 @@ const FlowControl = {
output = "",
progress = 0;
state.forkOffset += state.progress + 1;
recipe.addOperations(subOpList);
// Take a deep(ish) copy of the ingredient values
const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.getIngValues())));
// Run recipe over each tranche
for (i = 0; i < inputs.length; i++) {
log.debug(`Entering tranche ${i + 1} of ${inputs.length}`);
// Baseline ing values for each tranche so that registers are reset
subOpList.forEach((op, i) => {
op.setIngValues(JSON.parse(JSON.stringify(ingValues[i])));
});
const dish = new Dish(inputs[i], inputType);
try {
progress = await recipe.execute(dish, 0);
progress = await recipe.execute(dish, 0, state);
} catch (err) {
if (!ignoreErrors) {
throw err;
@@ -116,7 +128,7 @@ const FlowControl = {
if (!registers) return state;
if (ENVIRONMENT_IS_WORKER()) {
self.setRegisters(state.progress, state.numRegisters, registers.slice(1));
self.setRegisters(state.forkOffset + state.progress, state.numRegisters, registers.slice(1));
}
/**
@@ -169,16 +181,19 @@ const FlowControl = {
* @returns {Object} The updated state of the recipe.
*/
runJump: function(state) {
let ings = state.opList[state.progress].getIngValues(),
jmpIndex = FlowControl._getLabelIndex(ings[0], state),
maxJumps = ings[1];
const ings = state.opList[state.progress].getIngValues(),
label = ings[0],
maxJumps = ings[1],
jmpIndex = FlowControl._getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
log.debug("Maximum jumps reached or label cannot be found");
return state;
}
state.progress = jmpIndex;
state.numJumps++;
log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`);
return state;
},
@@ -194,14 +209,16 @@ const FlowControl = {
* @returns {Object} The updated state of the recipe.
*/
runCondJump: function(state) {
let ings = state.opList[state.progress].getIngValues(),
const ings = state.opList[state.progress].getIngValues(),
dish = state.dish,
regexStr = ings[0],
invert = ings[1],
jmpIndex = FlowControl._getLabelIndex(ings[2], state),
maxJumps = ings[3];
label = ings[2],
maxJumps = ings[3],
jmpIndex = FlowControl._getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
log.debug("Maximum jumps reached or label cannot be found");
return state;
}
@@ -210,6 +227,7 @@ const FlowControl = {
if (!invert && strMatch || invert && !strMatch) {
state.progress = jmpIndex;
state.numJumps++;
log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`);
}
}
@@ -249,6 +267,7 @@ const FlowControl = {
/**
* Returns the index of a label.
*
* @private
* @param {Object} state
* @param {string} name
* @returns {number}

View File

@@ -141,23 +141,31 @@ Recipe.prototype.lastOpIndex = function(startIndex) {
*
* @param {Dish} dish
* @param {number} [startFrom=0] - The index of the Operation to start executing from
* @param {number} [forkState={}] - If this is a forked recipe, the state of the recipe up to this point
* @returns {number} - The final progress through the recipe
*/
Recipe.prototype.execute = async function(dish, startFrom) {
startFrom = startFrom || 0;
let op, input, output, numJumps = 0, numRegisters = 0;
Recipe.prototype.execute = async function(dish, startFrom = 0, forkState = {}) {
let op, input, output,
numJumps = 0,
numRegisters = forkState.numRegisters || 0;
log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
for (let i = startFrom; i < this.opList.length; i++) {
op = this.opList[i];
log.debug(`[${i}] ${op.name} ${JSON.stringify(op.getIngValues())}`);
if (op.isDisabled()) {
log.debug("Operation is disabled, skipping");
continue;
}
if (op.isBreakpoint()) {
log.debug("Pausing at breakpoint");
return i;
}
try {
input = dish.get(op.inputType);
log.debug("Executing operation");
if (op.isFlowControl()) {
// Package up the current state
@@ -166,7 +174,8 @@ Recipe.prototype.execute = async function(dish, startFrom) {
"dish": dish,
"opList": this.opList,
"numJumps": numJumps,
"numRegisters": numRegisters
"numRegisters": numRegisters,
"forkOffset": forkState.forkOffset || 0
};
state = await op.run(state);
@@ -193,6 +202,7 @@ Recipe.prototype.execute = async function(dish, startFrom) {
}
}
log.debug("Recipe complete");
return this.opList.length;
};
@@ -250,4 +260,5 @@ Recipe.prototype.generateHighlightList = function() {
return highlights;
};
export default Recipe;

View File

@@ -201,7 +201,7 @@ const Utils = {
* Utils.parseEscapedChars("\\n");
*/
parseEscapedChars: function(str) {
return str.replace(/(\\)?\\([nrtbf]|x[\da-f]{2})/g, function(m, a, b) {
return str.replace(/(\\)?\\([nrtbf]|x[\da-fA-F]{2})/g, function(m, a, b) {
if (a === "\\") return "\\"+b;
switch (b[0]) {
case "n":
@@ -283,18 +283,18 @@ const Utils = {
/**
* Coverts data of varying types to a byteArray.
* Accepts hex, Base64, UTF8 and Latin1 strings.
*
*
* @param {string} str
* @param {string} type - One of "Hex", "Base64", "UTF8" or "Latin1"
* @returns {byteArray}
*
*
* @example
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("Привет", "utf8");
*
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex");
*
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64");
*/
@@ -313,6 +313,39 @@ const Utils = {
},
/**
* Coverts data of varying types to a byte string.
* Accepts hex, Base64, UTF8 and Latin1 strings.
*
* @param {string} str
* @param {string} type - One of "Hex", "Base64", "UTF8" or "Latin1"
* @returns {string}
*
* @example
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("Привет", "utf8");
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex");
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64");
*/
convertToByteString: function(str, type) {
switch (type.toLowerCase()) {
case "hex":
return Utils.byteArrayToChars(Utils.fromHex(str));
case "base64":
return Utils.byteArrayToChars(Utils.fromBase64(str, null, "byteArray"));
case "utf8":
return utf8.encode(str);
case "latin1":
default:
return str;
}
},
/**
* Converts a string to a byte array.
* Treats the string as UTF-8 if any values are over 255.
@@ -460,10 +493,10 @@ const Utils = {
/**
* Converts an ArrayBuffer to a string.
*
*
* @param {ArrayBuffer} arrayBuffer
* @returns {string}
*
*
* @example
* // returns "hello"
* Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
@@ -870,9 +903,9 @@ const Utils = {
* "Pretty" CyberChef recipe formats are designed to be included in the fragment (#) or query (?)
* parts of the URL. They can also be loaded into CyberChef through the 'Load' interface. In order
* to make this format as readable as possible, various special characters are used unescaped. This
* reduces the amount of percent-encoding included in the URL which is typically difficult to read,
* as well as substantially increasing the overall length. These characteristics can be quite
* offputting for users.
* reduces the amount of percent-encoding included in the URL which is typically difficult to read
* and substantially increases the overall length. These characteristics can be quite off-putting
* for users.
*
* @param {Object[]} recipeConfig
* @param {boolean} newline - whether to add a newline after each operation
@@ -889,12 +922,11 @@ const Utils = {
name = op.op.replace(/ /g, "_");
args = JSON.stringify(op.args)
.slice(1, -1) // Remove [ and ] as they are implied
// We now need to switch double-quoted (") strings to single-quotes (') as these do not
// need to be percent-encoded.
// We now need to switch double-quoted (") strings to single quotes (') as single quotes
// do not need to be percent-encoded.
.replace(/'/g, "\\'") // Escape single quotes
.replace(/\\"/g, '"') // Unescape double quotes
.replace(/(^|,|{|:)"/g, "$1'") // Replace opening " with '
.replace(/"(,|:|}|$)/g, "'$1"); // Replace closing " with '
.replace(/"((?:[^"\\]|\\.)*)"/g, "'$1'") // Replace opening and closing " with '
.replace(/\\"/g, '"'); // Unescape double quotes
disabled = op.disabled ? "/disabled": "";
bp = op.breakpoint ? "/breakpoint" : "";

View File

@@ -79,8 +79,8 @@ const Categories = [
"DES Decrypt",
"Triple DES Encrypt",
"Triple DES Decrypt",
"Rabbit Encrypt",
"Rabbit Decrypt",
"RC2 Encrypt",
"RC2 Decrypt",
"RC4",
"RC4 Drop",
"ROT13",
@@ -99,6 +99,7 @@ const Categories = [
"Substitute",
"Derive PBKDF2 key",
"Derive EVP key",
"Pseudo-Random Number Generator",
]
},
{
@@ -188,6 +189,7 @@ const Categories = [
"Find / Replace",
"Regular expression",
"Offset checker",
"Hamming Distance",
"Convert distance",
"Convert area",
"Convert mass",
@@ -198,6 +200,8 @@ const Categories = [
"Parse colour code",
"Escape string",
"Unescape string",
"Pseudo-Random Number Generator",
"Sleep",
]
},
{
@@ -210,6 +214,7 @@ const Categories = [
"Windows Filetime to UNIX Timestamp",
"UNIX Timestamp to Windows Filetime",
"Extract dates",
"Sleep",
]
},
{
@@ -313,6 +318,7 @@ const Categories = [
"Detect File Type",
"Scan for Embedded Files",
"Disassemble x86",
"Pseudo-Random Number Generator",
"Generate UUID",
"Generate TOTP",
"Generate HOTP",
@@ -320,6 +326,7 @@ const Categories = [
"Remove EXIF",
"Extract EXIF",
"Numberwang",
"XKCD Random Number",
]
},
{

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,6 @@ import CharEnc from "../../operations/CharEnc.js";
*
* Libraries:
* - cptable
* - CryptoJS
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017

View File

@@ -7,6 +7,7 @@ import Cipher from "../../operations/Cipher.js";
* Libraries:
* - CryptoJS
* - Blowfish
* - Forge
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
@@ -23,12 +24,12 @@ OpModules.Ciphers = {
"DES Decrypt": Cipher.runDesDec,
"Triple DES Encrypt": Cipher.runTripleDesEnc,
"Triple DES Decrypt": Cipher.runTripleDesDec,
"Rabbit Encrypt": Cipher.runRabbitEnc,
"Rabbit Decrypt": Cipher.runRabbitDec,
"Derive PBKDF2 key": Cipher.runPbkdf2,
"Derive EVP key": Cipher.runEvpkdf,
"RC4": Cipher.runRc4,
"RC4 Drop": Cipher.runRc4drop,
"RC2 Encrypt": Cipher.runRc2Enc,
"RC2 Decrypt": Cipher.runRc2Dec,
"Vigenère Encode": Cipher.runVigenereEnc,
"Vigenère Decode": Cipher.runVigenereDec,
"Bifid Cipher Encode": Cipher.runBifidEnc,
@@ -37,6 +38,7 @@ OpModules.Ciphers = {
"Affine Cipher Decode": Cipher.runAffineDec,
"Atbash Cipher": Cipher.runAtbash,
"Substitute": Cipher.runSubstitute,
"Pseudo-Random Number Generator": Cipher.runPRNG,
};
export default OpModules;

View File

@@ -11,7 +11,7 @@ import Code from "../../operations/Code.js";
* - xmldom
* - xpath
* - jpath
* - googlecodeprettify
* - highlight.js
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017

View File

@@ -10,7 +10,7 @@ import Convert from "../../operations/Convert.js";
import DateTime from "../../operations/DateTime.js";
import Endian from "../../operations/Endian.js";
import Entropy from "../../operations/Entropy.js";
import Extract from "../../operations/Extract.js";
import Filetime from "../../operations/Filetime.js";
import FileType from "../../operations/FileType.js";
import Hexdump from "../../operations/Hexdump.js";
import HTML from "../../operations/HTML.js";
@@ -29,6 +29,8 @@ import StrUtils from "../../operations/StrUtils.js";
import Tidy from "../../operations/Tidy.js";
import Unicode from "../../operations/Unicode.js";
import UUID from "../../operations/UUID.js";
import XKCD from "../../operations/XKCD.js";
/**
* Default module.
@@ -40,6 +42,7 @@ import UUID from "../../operations/UUID.js";
* - Utils.js
* - otp
* - crypto
* - bignumber.js
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
@@ -99,17 +102,16 @@ OpModules.Default = {
"Format MAC addresses": MAC.runFormat,
"Encode NetBIOS Name": NetBIOS.runEncodeName,
"Decode NetBIOS Name": NetBIOS.runDecodeName,
"Regular expression": StrUtils.runRegex,
"Offset checker": StrUtils.runOffsetChecker,
"To Upper case": StrUtils.runUpper,
"To Lower case": StrUtils.runLower,
"Find / Replace": StrUtils.runFindReplace,
"Split": StrUtils.runSplit,
"Filter": StrUtils.runFilter,
"Escape string": StrUtils.runEscape,
"Unescape string": StrUtils.runUnescape,
"Head": StrUtils.runHead,
"Tail": StrUtils.runTail,
"Hamming Distance": StrUtils.runHamming,
"Remove whitespace": Tidy.runRemoveWhitespace,
"Remove null bytes": Tidy.runRemoveNulls,
"Drop bytes": Tidy.runDropBytes,
@@ -132,14 +134,7 @@ OpModules.Default = {
"Translate DateTime Format": DateTime.runTranslateFormat,
"From UNIX Timestamp": DateTime.runFromUnixTimestamp,
"To UNIX Timestamp": DateTime.runToUnixTimestamp,
"Strings": Extract.runStrings,
"Extract IP addresses": Extract.runIp,
"Extract email addresses": Extract.runEmail,
"Extract MAC addresses": Extract.runMac,
"Extract URLs": Extract.runUrls,
"Extract domains": Extract.runDomains,
"Extract file paths": Extract.runFilePaths,
"Extract dates": Extract.runDates,
"Sleep": DateTime.runSleep,
"Microsoft Script Decoder": MS.runDecodeScript,
"Entropy": Entropy.runEntropy,
"Frequency distribution": Entropy.runFreqDistrib,
@@ -166,6 +161,9 @@ OpModules.Default = {
"Mean": Arithmetic.runMean,
"Median": Arithmetic.runMedian,
"Standard Deviation": Arithmetic.runStdDev,
"Windows Filetime to UNIX Timestamp": Filetime.runFromFiletimeToUnix,
"UNIX Timestamp to Windows Filetime": Filetime.runToFiletimeFromUnix,
"XKCD Random Number": XKCD.runRandomNumber,
/*

View File

@@ -1,5 +1,4 @@
import IP from "../../operations/IP.js";
import Filetime from "../../operations/Filetime.js";
/**
@@ -21,8 +20,6 @@ OpModules.JSBN = {
"Parse IPv4 header": IP.runParseIPv4Header,
"Change IP format": IP.runChangeIpFormat,
"Group IP addresses": IP.runGroupIps,
"Windows Filetime to UNIX Timestamp": Filetime.runFromFiletimeToUnix,
"UNIX Timestamp to Windows Filetime": Filetime.runToFiletimeFromUnix,
};
export default OpModules;

View File

@@ -18,6 +18,7 @@ import HTTPModule from "./HTTP.js";
import ImageModule from "./Image.js";
import JSBNModule from "./JSBN.js";
import PublicKeyModule from "./PublicKey.js";
import RegexModule from "./Regex.js";
import ShellcodeModule from "./Shellcode.js";
import URLModule from "./URL.js";
@@ -34,6 +35,7 @@ Object.assign(
ImageModule,
JSBNModule,
PublicKeyModule,
RegexModule,
ShellcodeModule,
URLModule
);

View File

@@ -0,0 +1,30 @@
import Extract from "../../operations/Extract.js";
import Regex from "../../operations/Regex.js";
/**
* Regex module.
*
* Libraries:
* - XRegExp
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Regex = {
"Regular expression": Regex.runRegex,
"Find / Replace": Regex.runFindReplace,
"Strings": Extract.runStrings,
"Extract IP addresses": Extract.runIp,
"Extract email addresses": Extract.runEmail,
"Extract MAC addresses": Extract.runMac,
"Extract URLs": Extract.runUrls,
"Extract domains": Extract.runDomains,
"Extract file paths": Extract.runFilePaths,
"Extract dates": Extract.runDates,
};
export default OpModules;

View File

@@ -1,4 +1,5 @@
import Utils from "../Utils.js";
import BigNumber from "bignumber.js";
/**
@@ -24,11 +25,11 @@ const Arithmetic = {
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runSum: function(input, args) {
const val = Arithmetic._sum(Arithmetic._createNumArray(input, args[0]));
return typeof(val) === "number" ? val : NaN;
return val instanceof BigNumber ? val : new BigNumber(NaN);
},
@@ -37,11 +38,11 @@ const Arithmetic = {
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runSub: function(input, args) {
let val = Arithmetic._sub(Arithmetic._createNumArray(input, args[0]));
return typeof(val) === "number" ? val : NaN;
return val instanceof BigNumber ? val : new BigNumber(NaN);
},
@@ -50,11 +51,11 @@ const Arithmetic = {
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runMulti: function(input, args) {
let val = Arithmetic._multi(Arithmetic._createNumArray(input, args[0]));
return typeof(val) === "number" ? val : NaN;
return val instanceof BigNumber ? val : new BigNumber(NaN);
},
@@ -63,11 +64,11 @@ const Arithmetic = {
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runDiv: function(input, args) {
let val = Arithmetic._div(Arithmetic._createNumArray(input, args[0]));
return typeof(val) === "number" ? val : NaN;
return val instanceof BigNumber ? val : new BigNumber(NaN);
},
@@ -76,11 +77,11 @@ const Arithmetic = {
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runMean: function(input, args) {
let val = Arithmetic._mean(Arithmetic._createNumArray(input, args[0]));
return typeof(val) === "number" ? val : NaN;
return val instanceof BigNumber ? val : new BigNumber(NaN);
},
@@ -89,11 +90,11 @@ const Arithmetic = {
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runMedian: function(input, args) {
let val = Arithmetic._median(Arithmetic._createNumArray(input, args[0]));
return typeof(val) === "number" ? val : NaN;
return val instanceof BigNumber ? val : new BigNumber(NaN);
},
@@ -102,11 +103,11 @@ const Arithmetic = {
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runStdDev: function(input, args) {
let val = Arithmetic._stdDev(Arithmetic._createNumArray(input, args[0]));
return typeof(val) === "number" ? val : NaN;
return val instanceof BigNumber ? val : new BigNumber(NaN);
},
@@ -116,7 +117,7 @@ const Arithmetic = {
* @private
* @param {string[]} input
* @param {string} delim
* @returns {number[]}
* @returns {BigNumber[]}
*/
_createNumArray: function(input, delim) {
delim = Utils.charRep[delim || "Space"];
@@ -125,13 +126,13 @@ const Arithmetic = {
num;
for (let i = 0; i < splitNumbers.length; i++) {
if (splitNumbers[i].indexOf(".") >= 0) {
num = parseFloat(splitNumbers[i].trim());
} else {
num = parseInt(splitNumbers[i].trim(), 0);
}
if (!isNaN(num)) {
numbers.push(num);
try {
num = BigNumber(splitNumbers[i].trim());
if (!num.isNaN()) {
numbers.push(num);
}
} catch (err) {
// This line is not a valid number
}
}
return numbers;
@@ -142,12 +143,12 @@ const Arithmetic = {
* Adds an array of numbers and returns the value.
*
* @private
* @param {number[]} data
* @returns {number}
* @param {BigNumber[]} data
* @returns {BigNumber}
*/
_sum: function(data) {
if (data.length > 0) {
return data.reduce((acc, curr) => acc + curr);
return data.reduce((acc, curr) => acc.plus(curr));
}
},
@@ -156,12 +157,12 @@ const Arithmetic = {
* Subtracts an array of numbers and returns the value.
*
* @private
* @param {number[]} data
* @returns {number}
* @param {BigNumber[]} data
* @returns {BigNumber}
*/
_sub: function(data) {
if (data.length > 0) {
return data.reduce((acc, curr) => acc - curr);
return data.reduce((acc, curr) => acc.minus(curr));
}
},
@@ -170,12 +171,12 @@ const Arithmetic = {
* Multiplies an array of numbers and returns the value.
*
* @private
* @param {number[]} data
* @returns {number}
* @param {BigNumber[]} data
* @returns {BigNumber}
*/
_multi: function(data) {
if (data.length > 0) {
return data.reduce((acc, curr) => acc * curr);
return data.reduce((acc, curr) => acc.times(curr));
}
},
@@ -184,12 +185,12 @@ const Arithmetic = {
* Divides an array of numbers and returns the value.
*
* @private
* @param {number[]} data
* @returns {number}
* @param {BigNumber[]} data
* @returns {BigNumber}
*/
_div: function(data) {
if (data.length > 0) {
return data.reduce((acc, curr) => acc / curr);
return data.reduce((acc, curr) => acc.div(curr));
}
},
@@ -198,12 +199,12 @@ const Arithmetic = {
* Computes mean of a number array and returns the value.
*
* @private
* @param {number[]} data
* @returns {number}
* @param {BigNumber[]} data
* @returns {BigNumber}
*/
_mean: function(data) {
if (data.length > 0) {
return Arithmetic._sum(data) / data.length;
return Arithmetic._sum(data).div(data.length);
}
},
@@ -212,14 +213,14 @@ const Arithmetic = {
* Computes median of a number array and returns the value.
*
* @private
* @param {number[]} data
* @returns {number}
* @param {BigNumber[]} data
* @returns {BigNumber}
*/
_median: function (data) {
if ((data.length % 2) === 0) {
if ((data.length % 2) === 0 && data.length > 0) {
let first, second;
data.sort(function(a, b){
return a - b;
return a.minus(b);
});
first = data[Math.floor(data.length / 2)];
second = data[Math.floor(data.length / 2) - 1];
@@ -234,17 +235,17 @@ const Arithmetic = {
* Computes standard deviation of a number array and returns the value.
*
* @private
* @param {number[]} data
* @returns {number}
* @param {BigNumber[]} data
* @returns {BigNumber}
*/
_stdDev: function (data) {
if (data.length > 0) {
let avg = Arithmetic._mean(data);
let devSum = 0;
let devSum = new BigNumber(0);
for (let i = 0; i < data.length; i++) {
devSum += (data[i] - avg) ** 2;
devSum = devSum.plus(data[i].minus(avg).pow(2));
}
return Math.sqrt(devSum / data.length);
return devSum.div(data.length).sqrt();
}
},
};

View File

@@ -1,4 +1,5 @@
import Utils from "../Utils.js";
import BigNumber from "bignumber.js";
/**
@@ -61,14 +62,14 @@ const BCD = {
/**
* To BCD operation.
*
* @param {number} input
* @param {BigNumber} input
* @param {Object[]} args
* @returns {string}
*/
runToBCD: function(input, args) {
if (isNaN(input))
if (input.isNaN())
return "Invalid input";
if (Math.floor(input) !== input)
if (!input.integerValue(BigNumber.ROUND_DOWN).isEqualTo(input))
return "Fractional values are not supported by BCD";
const encoding = BCD.ENCODING_LOOKUP[args[0]],
@@ -77,7 +78,7 @@ const BCD = {
outputFormat = args[3];
// Split input number up into separate digits
const digits = input.toString().split("");
const digits = input.toFixed().split("");
if (digits[0] === "-" || digits[0] === "+") {
digits.shift();
@@ -152,7 +153,7 @@ const BCD = {
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runFromBCD: function(input, args) {
const encoding = BCD.ENCODING_LOOKUP[args[0]],
@@ -206,7 +207,7 @@ const BCD = {
output += val.toString();
});
return parseInt(output, 10);
return new BigNumber(output);
},
};

View File

@@ -1,3 +1,5 @@
import BigNumber from "bignumber.js";
/**
* Numerical base operations.
*
@@ -18,7 +20,7 @@ const Base = {
/**
* To Base operation.
*
* @param {number} input
* @param {BigNumber} input
* @param {Object[]} args
* @returns {string}
*/
@@ -39,7 +41,7 @@ const Base = {
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runFrom: function(input, args) {
const radix = args[0] || Base.DEFAULT_RADIX;
@@ -48,14 +50,14 @@ const Base = {
}
let number = input.replace(/\s/g, "").split("."),
result = parseInt(number[0], radix) || 0;
result = new BigNumber(number[0], radix) || 0;
if (number.length === 1) return result;
// Fractional part
for (let i = 0; i < number[1].length; i++) {
const digit = parseInt(number[1][i], radix);
result += digit / Math.pow(radix, i+1);
const digit = new BigNumber(number[1][i], radix);
result += digit.div(Math.pow(radix, i+1));
}
return result;

View File

@@ -1,6 +1,8 @@
import Utils from "../Utils.js";
import CryptoJS from "crypto-js";
import forge from "imports-loader?jQuery=>null!node-forge/dist/forge.min.js";
import {blowfish as Blowfish} from "sladex-blowfish";
import BigNumber from "bignumber.js";
/**
@@ -18,132 +20,27 @@ const Cipher = {
* @constant
* @default
*/
IO_FORMAT1: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"],
IO_FORMAT1: ["Hex", "UTF8", "Latin1", "Base64"],
/**
* @constant
* @default
*/
IO_FORMAT2: ["UTF8", "Latin1", "Hex", "Base64"],
/**
* @constant
* @default
*/
IO_FORMAT3: ["Raw", "Hex"],
/**
* @constant
* @default
*/
IO_FORMAT4: ["Hex", "Raw"],
/**
* @constant
* @default
*/
IO_FORMAT2: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"],
/**
* @constant
* @default
*/
IO_FORMAT3: ["Hex", "Base64", "UTF16", "UTF16LE", "UTF16BE", "Latin1"],
/**
* @constant
* @default
*/
IO_FORMAT4: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"],
/**
* @constant
* @default
*/
MODES: ["CBC", "CFB", "CTR", "OFB", "ECB"],
/**
* @constant
* @default
*/
PADDING: ["Pkcs7", "Iso97971", "AnsiX923", "Iso10126", "ZeroPadding", "NoPadding"],
/**
* @constant
* @default
*/
RESULT_TYPE: ["Show all", "Ciphertext", "Key", "IV", "Salt"],
/**
* Runs encryption operations using the CryptoJS framework.
*
* @private
* @param {function} algo - The CryptoJS algorithm to use
* @param {byteArray} input
* @param {function} args
* @returns {string}
*/
_enc: function (algo, input, args) {
let key = Cipher._format[args[0].option].parse(args[0].string || ""),
iv = Cipher._format[args[1].option].parse(args[1].string || ""),
salt = Cipher._format[args[2].option].parse(args[2].string || ""),
mode = CryptoJS.mode[args[3]],
padding = CryptoJS.pad[args[4]],
resultOption = args[5].toLowerCase(),
outputFormat = args[6];
if (iv.sigBytes === 0) {
// Use passphrase rather than key. Need to convert it to a string.
key = key.toString(CryptoJS.enc.Latin1);
}
const encrypted = algo.encrypt(input, key, {
salt: salt.sigBytes > 0 ? salt : false,
iv: iv.sigBytes > 0 ? iv : null,
mode: mode,
padding: padding
});
let result = "";
if (resultOption === "show all") {
result += "Key: " + encrypted.key.toString(Cipher._format[outputFormat]);
result += "\nIV: " + encrypted.iv.toString(Cipher._format[outputFormat]);
if (encrypted.salt) result += "\nSalt: " + encrypted.salt.toString(Cipher._format[outputFormat]);
result += "\n\nCiphertext: " + encrypted.ciphertext.toString(Cipher._format[outputFormat]);
} else {
result = encrypted[resultOption].toString(Cipher._format[outputFormat]);
}
return result;
},
/**
* Runs decryption operations using the CryptoJS framework.
*
* @private
* @param {function} algo - The CryptoJS algorithm to use
* @param {byteArray} input
* @param {function} args
* @returns {string}
*/
_dec: function (algo, input, args) {
let key = Cipher._format[args[0].option].parse(args[0].string || ""),
iv = Cipher._format[args[1].option].parse(args[1].string || ""),
salt = Cipher._format[args[2].option].parse(args[2].string || ""),
mode = CryptoJS.mode[args[3]],
padding = CryptoJS.pad[args[4]],
inputFormat = args[5],
outputFormat = args[6];
// The ZeroPadding option causes a crash when the input length is 0
if (!input.length) {
return "No input";
}
const ciphertext = Cipher._format[inputFormat].parse(input);
if (iv.sigBytes === 0) {
// Use passphrase rather than key. Need to convert it to a string.
key = key.toString(CryptoJS.enc.Latin1);
}
const decrypted = algo.decrypt({
ciphertext: ciphertext,
salt: salt.sigBytes > 0 ? salt : false
}, key, {
iv: iv.sigBytes > 0 ? iv : null,
mode: mode,
padding: padding
});
let result;
try {
result = decrypted.toString(Cipher._format[outputFormat]);
} catch (err) {
result = "Decrypt error: " + err.message;
}
return result;
},
AES_MODES: ["CBC", "CFB", "OFB", "CTR", "GCM", "ECB"],
/**
* AES Encrypt operation.
@@ -153,7 +50,41 @@ const Cipher = {
* @returns {string}
*/
runAesEnc: function (input, args) {
return Cipher._enc(CryptoJS.AES, input, args);
const key = Utils.convertToByteArray(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if ([16, 24, 32].indexOf(key.length) < 0) {
return `Invalid key length: ${key.length} bytes
The following algorithms will be used based on the size of the key:
16 bytes = AES-128
24 bytes = AES-192
32 bytes = AES-256`;
}
input = Utils.convertToByteString(input, inputType);
const cipher = forge.cipher.createCipher("AES-" + mode, key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(input));
cipher.finish();
if (outputType === "Hex") {
if (mode === "GCM") {
return cipher.output.toHex() + "\n\n" +
"Tag: " + cipher.mode.tag.toHex();
}
return cipher.output.toHex();
} else {
if (mode === "GCM") {
return cipher.output.getBytes() + "\n\n" +
"Tag: " + cipher.mode.tag.getBytes();
}
return cipher.output.getBytes();
}
},
@@ -165,10 +96,46 @@ const Cipher = {
* @returns {string}
*/
runAesDec: function (input, args) {
return Cipher._dec(CryptoJS.AES, input, args);
const key = Utils.convertToByteArray(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4],
gcmTag = Utils.convertToByteString(args[5].string, args[5].option);
if ([16, 24, 32].indexOf(key.length) < 0) {
return `Invalid key length: ${key.length} bytes
The following algorithms will be used based on the size of the key:
16 bytes = AES-128
24 bytes = AES-192
32 bytes = AES-256`;
}
input = Utils.convertToByteString(input, inputType);
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
decipher.start({
iv: iv,
tag: gcmTag
});
decipher.update(forge.util.createBuffer(input));
const result = decipher.finish();
if (result) {
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
} else {
return "Unable to decrypt input with these parameters.";
}
},
/**
* @constant
* @default
*/
DES_MODES: ["CBC", "CFB", "OFB", "CTR", "ECB"],
/**
* DES Encrypt operation.
*
@@ -177,7 +144,27 @@ const Cipher = {
* @returns {string}
*/
runDesEnc: function (input, args) {
return Cipher._enc(CryptoJS.DES, input, args);
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length !== 8) {
return `Invalid key length: ${key.length} bytes
DES uses a key length of 8 bytes (64 bits).
Triple DES uses a key length of 24 bytes (192 bits).`;
}
input = Utils.convertToByteString(input, inputType);
const cipher = forge.cipher.createCipher("DES-" + mode, key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(input));
cipher.finish();
return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
},
@@ -189,7 +176,31 @@ const Cipher = {
* @returns {string}
*/
runDesDec: function (input, args) {
return Cipher._dec(CryptoJS.DES, input, args);
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length !== 8) {
return `Invalid key length: ${key.length} bytes
DES uses a key length of 8 bytes (64 bits).
Triple DES uses a key length of 24 bytes (192 bits).`;
}
input = Utils.convertToByteString(input, inputType);
const decipher = forge.cipher.createDecipher("DES-" + mode, key);
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(input));
const result = decipher.finish();
if (result) {
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
} else {
return "Unable to decrypt input with these parameters.";
}
},
@@ -201,7 +212,27 @@ const Cipher = {
* @returns {string}
*/
runTripleDesEnc: function (input, args) {
return Cipher._enc(CryptoJS.TripleDES, input, args);
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length !== 24) {
return `Invalid key length: ${key.length} bytes
Triple DES uses a key length of 24 bytes (192 bits).
DES uses a key length of 8 bytes (64 bits).`;
}
input = Utils.convertToByteString(input, inputType);
const cipher = forge.cipher.createCipher("3DES-" + mode, key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(input));
cipher.finish();
return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
},
@@ -213,31 +244,79 @@ const Cipher = {
* @returns {string}
*/
runTripleDesDec: function (input, args) {
return Cipher._dec(CryptoJS.TripleDES, input, args);
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length !== 24) {
return `Invalid key length: ${key.length} bytes
Triple DES uses a key length of 24 bytes (192 bits).
DES uses a key length of 8 bytes (64 bits).`;
}
input = Utils.convertToByteString(input, inputType);
const decipher = forge.cipher.createDecipher("3DES-" + mode, key);
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(input));
const result = decipher.finish();
if (result) {
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
} else {
return "Unable to decrypt input with these parameters.";
}
},
/**
* Rabbit Encrypt operation.
* RC2 Encrypt operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runRabbitEnc: function (input, args) {
return Cipher._enc(CryptoJS.Rabbit, input, args);
runRc2Enc: function (input, args) {
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteString(args[1].string, args[1].option),
inputType = args[2],
outputType = args[3],
cipher = forge.rc2.createEncryptionCipher(key);
input = Utils.convertToByteString(input, inputType);
cipher.start(iv || null);
cipher.update(forge.util.createBuffer(input));
cipher.finish();
return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
},
/**
* Rabbit Decrypt operation.
* RC2 Decrypt operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runRabbitDec: function (input, args) {
return Cipher._dec(CryptoJS.Rabbit, input, args);
runRc2Dec: function (input, args) {
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteString(args[1].string, args[1].option),
inputType = args[2],
outputType = args[3],
decipher = forge.rc2.createDecryptionCipher(key);
input = Utils.convertToByteString(input, inputType);
decipher.start(iv || null);
decipher.update(forge.util.createBuffer(input));
decipher.finish();
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
},
@@ -245,12 +324,29 @@ const Cipher = {
* @constant
* @default
*/
BLOWFISH_MODES: ["ECB", "CBC", "PCBC", "CFB", "OFB", "CTR"],
BLOWFISH_MODES: ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"],
/**
* @constant
* @default
*/
BLOWFISH_OUTPUT_TYPES: ["Base64", "Hex", "String", "Raw"],
BLOWFISH_OUTPUT_TYPES: ["Hex", "Base64", "Raw"],
/**
* Lookup table for Blowfish output types.
*
* @private
*/
_BLOWFISH_OUTPUT_TYPE_LOOKUP: {
Base64: 0, Hex: 1, String: 2, Raw: 3
},
/**
* Lookup table for Blowfish modes.
*
* @private
*/
_BLOWFISH_MODE_LOOKUP: {
ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5
},
/**
* Blowfish Encrypt operation.
@@ -260,19 +356,24 @@ const Cipher = {
* @returns {string}
*/
runBlowfishEnc: function (input, args) {
let key = Cipher._format[args[0].option].parse(args[0].string).toString(Cipher._format.Latin1),
mode = args[1],
outputFormat = args[2];
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length === 0) return "Enter a key";
let encHex = Blowfish.encrypt(input, key, {
outputType: 1,
cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode)
}),
enc = CryptoJS.enc.Hex.parse(encHex);
input = Utils.convertToByteString(input, inputType);
return enc.toString(Cipher._format[outputFormat]);
Blowfish.setIV(Utils.toBase64(iv), 0);
const enc = Blowfish.encrypt(input, key, {
outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[outputType],
cipherMode: Cipher._BLOWFISH_MODE_LOOKUP[mode]
});
return outputType === "Raw" ? Utils.byteArrayToChars(enc) : enc ;
},
@@ -284,18 +385,24 @@ const Cipher = {
* @returns {string}
*/
runBlowfishDec: function (input, args) {
let key = Cipher._format[args[0].option].parse(args[0].string).toString(Cipher._format.Latin1),
mode = args[1],
inputFormat = args[2];
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length === 0) return "Enter a key";
input = Cipher._format[inputFormat].parse(input);
input = inputType === "Raw" ? Utils.strToByteArray(input) : input;
return Blowfish.decrypt(input.toString(CryptoJS.enc.Base64), key, {
outputType: 0, // This actually means inputType. The library is weird.
cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode)
Blowfish.setIV(Utils.toBase64(iv), 0);
const result = Blowfish.decrypt(input, key, {
outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[inputType], // This actually means inputType. The library is weird.
cipherMode: Cipher._BLOWFISH_MODE_LOOKUP[mode]
});
return outputType === "Hex" ? Utils.toHexFast(Utils.strToByteArray(result)) : result;
},
@@ -303,7 +410,7 @@ const Cipher = {
* @constant
* @default
*/
KDF_KEY_SIZE: 256,
KDF_KEY_SIZE: 128,
/**
* @constant
* @default
@@ -313,7 +420,7 @@ const Cipher = {
* @constant
* @default
*/
HASHERS: ["MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "RIPEMD160"],
HASHERS: ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"],
/**
* Derive PBKDF2 key operation.
@@ -323,20 +430,15 @@ const Cipher = {
* @returns {string}
*/
runPbkdf2: function (input, args) {
let keySize = args[0] / 32,
iterations = args[1],
hasher = args[2],
salt = CryptoJS.enc.Hex.parse(args[3] || ""),
inputFormat = args[4],
outputFormat = args[5],
passphrase = Cipher._format[inputFormat].parse(input),
key = CryptoJS.PBKDF2(passphrase, salt, {
keySize: keySize,
hasher: CryptoJS.algo[hasher],
iterations: iterations,
});
const passphrase = Utils.convertToByteString(args[0].string, args[0].option),
keySize = args[1],
iterations = args[2],
hasher = args[3],
salt = Utils.convertToByteString(args[4].string, args[4].option) ||
forge.random.getBytesSync(keySize),
derivedKey = forge.pkcs5.pbkdf2(passphrase, salt, iterations, keySize / 8, hasher.toLowerCase());
return key.toString(Cipher._format[outputFormat]);
return forge.util.bytesToHex(derivedKey);
},
@@ -348,23 +450,33 @@ const Cipher = {
* @returns {string}
*/
runEvpkdf: function (input, args) {
let keySize = args[0] / 32,
iterations = args[1],
hasher = args[2],
salt = CryptoJS.enc.Hex.parse(args[3] || ""),
inputFormat = args[4],
outputFormat = args[5],
passphrase = Cipher._format[inputFormat].parse(input),
const passphrase = Utils.convertToByteString(args[0].string, args[0].option),
keySize = args[1] / 32,
iterations = args[2],
hasher = args[3],
salt = Utils.convertToByteString(args[4].string, args[4].option),
key = CryptoJS.EvpKDF(passphrase, salt, {
keySize: keySize,
hasher: CryptoJS.algo[hasher],
iterations: iterations,
});
return key.toString(Cipher._format[outputFormat]);
return key.toString(CryptoJS.enc.Hex);
},
/**
* @constant
* @default
*/
RC4_KEY_FORMAT: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"],
/**
* @constant
* @default
*/
CJS_IO_FORMAT: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"],
/**
* RC4 operation.
*
@@ -404,6 +516,57 @@ const Cipher = {
},
/**
* @constant
* @default
*/
PRNG_BYTES: 32,
/**
* @constant
* @default
*/
PRNG_OUTPUT: ["Hex", "Integer", "Byte array", "Raw"],
/**
* Pseudo-Random Number Generator operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runPRNG: function(input, args) {
const numBytes = args[0],
outputAs = args[1];
let bytes;
if (ENVIRONMENT_IS_WORKER() && self.crypto) {
bytes = self.crypto.getRandomValues(new Uint8Array(numBytes));
bytes = Utils.arrayBufferToStr(bytes.buffer);
} else {
bytes = forge.random.getBytesSync(numBytes);
}
let value = new BigNumber(0),
i;
switch (outputAs) {
case "Hex":
return forge.util.bytesToHex(bytes);
case "Integer":
for (i = bytes.length - 1; i >= 0; i--) {
value = value.times(256).plus(bytes.charCodeAt(i));
}
return value.toFixed();
case "Byte array":
return JSON.stringify(Utils.strToCharcode(bytes));
case "Raw":
default:
return bytes;
}
},
/**
* Vigenère Encode operation.
*
@@ -786,7 +949,7 @@ const Cipher = {
/**
* A mapping of string formats to their classes in the CryptoJS library.
*
*
* @private
* @constant
*/

View File

@@ -1,12 +1,10 @@
import {camelCase, kebabCase, snakeCase} from "lodash";
import Utils from "../Utils.js";
import vkbeautify from "vkbeautify";
import {DOMParser} from "xmldom";
import xpath from "xpath";
import jpath from "jsonpath";
import nwmatcher from "nwmatcher";
import prettyPrintOne from "imports-loader?window=>global!exports-loader?prettyPrintOne!google-code-prettify/bin/prettify.min.js";
import hljs from "highlight.js";
/**
@@ -24,12 +22,7 @@ const Code = {
* @constant
* @default
*/
LANGUAGES: ["default-code", "default-markup", "bash", "bsh", "c", "cc", "coffee", "cpp", "cs", "csh", "cv", "cxx", "cyc", "htm", "html", "in.tag", "java", "javascript", "js", "json", "m", "mxml", "perl", "pl", "pm", "py", "python", "rb", "rc", "rs", "ruby", "rust", "sh", "uq.val", "xhtml", "xml", "xsl"],
/**
* @constant
* @default
*/
LINE_NUMS: false,
LANGUAGES: ["auto detect"].concat(hljs.listLanguages()),
/**
* Syntax highlighter operation.
@@ -39,9 +32,13 @@ const Code = {
* @returns {html}
*/
runSyntaxHighlight: function(input, args) {
let language = args[0],
lineNums = args[1];
return "<code class='prettyprint'>" + prettyPrintOne(Utils.escapeHtml(input), language, lineNums) + "</code>";
const language = args[0];
if (language === "auto detect") {
return hljs.highlightAuto(input).value;
}
return hljs.highlight(language, input, true).value;
},

View File

@@ -60,17 +60,16 @@ const Convert = {
/**
* Convert distance operation.
*
* @param {number} input
* @param {BigNumber} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runDistance: function (input, args) {
let inputUnits = args[0],
outputUnits = args[1];
input = input * Convert.DISTANCE_FACTOR[inputUnits];
return input / Convert.DISTANCE_FACTOR[outputUnits];
// TODO Remove rounding errors (e.g. 1.000000000001)
input = input.times(Convert.DISTANCE_FACTOR[inputUnits]);
return input.div(Convert.DISTANCE_FACTOR[outputUnits]);
},
@@ -141,16 +140,16 @@ const Convert = {
/**
* Convert data units operation.
*
* @param {number} input
* @param {BigNumber} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runDataSize: function (input, args) {
let inputUnits = args[0],
outputUnits = args[1];
input = input * Convert.DATA_FACTOR[inputUnits];
return input / Convert.DATA_FACTOR[outputUnits];
input = input.times(Convert.DATA_FACTOR[inputUnits]);
return input.div(Convert.DATA_FACTOR[outputUnits]);
},
@@ -221,16 +220,16 @@ const Convert = {
/**
* Convert area operation.
*
* @param {number} input
* @param {BigNumber} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runArea: function (input, args) {
let inputUnits = args[0],
outputUnits = args[1];
input = input * Convert.AREA_FACTOR[inputUnits];
return input / Convert.AREA_FACTOR[outputUnits];
input = input.times(Convert.AREA_FACTOR[inputUnits]);
return input.div(Convert.AREA_FACTOR[outputUnits]);
},
@@ -332,16 +331,16 @@ const Convert = {
/**
* Convert mass operation.
*
* @param {number} input
* @param {BigNumber} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runMass: function (input, args) {
let inputUnits = args[0],
outputUnits = args[1];
input = input * Convert.MASS_FACTOR[inputUnits];
return input / Convert.MASS_FACTOR[outputUnits];
input = input.times(Convert.MASS_FACTOR[inputUnits]);
return input.div(Convert.MASS_FACTOR[outputUnits]);
},
@@ -397,16 +396,16 @@ const Convert = {
/**
* Convert speed operation.
*
* @param {number} input
* @param {BigNumber} input
* @param {Object[]} args
* @returns {number}
* @returns {BigNumber}
*/
runSpeed: function (input, args) {
let inputUnits = args[0],
outputUnits = args[1];
input = input * Convert.SPEED_FACTOR[inputUnits];
return input / Convert.SPEED_FACTOR[outputUnits];
input = input.times(Convert.SPEED_FACTOR[inputUnits]);
return input.div(Convert.SPEED_FACTOR[outputUnits]);
},
};

View File

@@ -189,6 +189,20 @@ const DateTime = {
},
/**
* Sleep operation.
*
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
runSleep: async function(input, args) {
const ms = args[0];
await new Promise(r => setTimeout(r, ms));
return input;
},
/**
* @constant
*/

View File

@@ -1,3 +1,6 @@
import XRegExp from "xregexp";
/**
* Identifier extraction operations.
*
@@ -26,6 +29,11 @@ const Extract = {
match;
while ((match = searchRegex.exec(input))) {
// Moves pointer when an empty string is matched (prevents infinite loop)
if (match.index === searchRegex.lastIndex) {
searchRegex.lastIndex++;
}
if (removeRegex && removeRegex.test(match[0]))
continue;
total++;
@@ -43,7 +51,20 @@ const Extract = {
* @constant
* @default
*/
MIN_STRING_LEN: 3,
MIN_STRING_LEN: 4,
/**
* @constant
* @default
*/
STRING_MATCH_TYPE: [
"[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)",
"[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)"
],
/**
* @constant
* @default
*/
ENCODING_LIST: ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"],
/**
* @constant
* @default
@@ -58,10 +79,59 @@ const Extract = {
* @returns {string}
*/
runStrings: function(input, args) {
let minLen = args[0] || Extract.MIN_STRING_LEN,
displayTotal = args[1],
strings = "[A-Z\\d/\\-:.,_$%'\"()<>= !\\[\\]{}@]",
regex = new RegExp(strings + "{" + minLen + ",}", "ig");
const encoding = args[0],
minLen = args[1],
matchType = args[2],
displayTotal = args[3],
alphanumeric = "A-Z\\d",
punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@",
printable = "\x20-\x7e",
uniAlphanumeric = "\\pL\\pN",
uniPunctuation = "\\pP\\pZ",
uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP";
let strings = "";
switch (matchType) {
case "Alphanumeric + punctuation (A)":
strings = `[${alphanumeric + punctuation}]`;
break;
case "All printable chars (A)":
case "Null-terminated strings (A)":
strings = `[${printable}]`;
break;
case "Alphanumeric + punctuation (U)":
strings = `[${uniAlphanumeric + uniPunctuation}]`;
break;
case "All printable chars (U)":
case "Null-terminated strings (U)":
strings = `[${uniPrintable}]`;
break;
}
// UTF-16 support is hacked in by allowing null bytes on either side of the matched chars
switch (encoding) {
case "All":
strings = `(\x00?${strings}\x00?)`;
break;
case "16-bit littleendian":
strings = `(${strings}\x00)`;
break;
case "16-bit bigendian":
strings = `(\x00${strings})`;
break;
case "Single byte":
default:
break;
}
strings = `${strings}{${minLen},}`;
if (matchType.includes("Null-terminated")) {
strings += "\x00";
}
const regex = new XRegExp(strings, "ig");
return Extract._search(input, regex, null, displayTotal);
},
@@ -217,7 +287,7 @@ const Extract = {
includeUnixPath = args[1],
displayTotal = args[2],
winDrive = "[A-Z]:\\\\",
winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)]{0,61}",
winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}",
winExt = "[A-Z\\d]{1,6}",
winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName +
"(?:\\." + winExt + ")?",

View File

@@ -1,4 +1,4 @@
import {BigInteger} from "jsbn";
import BigNumber from "bignumber.js";
/**
* Windows Filetime operations.
@@ -35,27 +35,29 @@ const Filetime = {
let units = args[0],
format = args[1];
if (!input) return "";
if (format === "Hex") {
input = new BigInteger(input, 16);
input = new BigNumber(input, 16);
} else {
input = new BigInteger(input);
input = new BigNumber(input);
}
input = input.subtract(new BigInteger("116444736000000000"));
input = input.minus(new BigNumber("116444736000000000"));
if (units === "Seconds (s)"){
input = input.divide(new BigInteger("10000000"));
input = input.dividedBy(new BigNumber("10000000"));
} else if (units === "Milliseconds (ms)") {
input = input.divide(new BigInteger("10000"));
input = input.dividedBy(new BigNumber("10000"));
} else if (units === "Microseconds (μs)") {
input = input.divide(new BigInteger("10"));
input = input.dividedBy(new BigNumber("10"));
} else if (units === "Nanoseconds (ns)") {
input = input.multiply(new BigInteger("100"));
input = input.multipliedBy(new BigNumber("100"));
} else {
throw "Unrecognised unit";
}
return input.toString();
return input.toFixed();
},
@@ -71,26 +73,28 @@ const Filetime = {
let units = args[0],
format = args[1];
input = new BigInteger(input);
if (!input) return "";
input = new BigNumber(input);
if (units === "Seconds (s)"){
input = input.multiply(new BigInteger("10000000"));
input = input.multipliedBy(new BigNumber("10000000"));
} else if (units === "Milliseconds (ms)") {
input = input.multiply(new BigInteger("10000"));
input = input.multipliedBy(new BigNumber("10000"));
} else if (units === "Microseconds (μs)") {
input = input.multiply(new BigInteger("10"));
input = input.multiplyiedBy(new BigNumber("10"));
} else if (units === "Nanoseconds (ns)") {
input = input.divide(new BigInteger("100"));
input = input.dividedBy(new BigNumber("100"));
} else {
throw "Unrecognised unit";
}
input = input.add(new BigInteger("116444736000000000"));
input = input.plus(new BigNumber("116444736000000000"));
if (format === "Hex"){
return input.toString(16);
} else {
return input.toString();
return input.toFixed();
}
},

View File

@@ -0,0 +1,278 @@
import XRegExp from "xregexp";
import Utils from "../Utils.js";
/**
* Regex operations.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*
* @namespace
*/
const Regex = {
/**
* @constant
* @default
*/
REGEX_PRE_POPULATE: [
{
name: "User defined",
value: ""
},
{
name: "IPv4 address",
value: "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?"
},
{
name: "IPv6 address",
value: "((?=.*::)(?!.*::.+::)(::)?([\\dA-Fa-f]{1,4}:(:|\\b)|){5}|([\\dA-Fa-f]{1,4}:){6})((([\\dA-Fa-f]{1,4}((?!\\3)::|:\\b|(?![\\dA-Fa-f])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})"
},
{
name: "Email address",
value: "(\\w[-.\\w]*)@([-\\w]+(?:\\.[-\\w]+)*)\\.([A-Za-z]{2,4})"
},
{
name: "URL",
value: "([A-Za-z]+://)([-\\w]+(?:\\.\\w[-\\w]*)+)(:\\d+)?(/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*)?"
},
{
name: "Domain",
value: "\\b((?=[a-z0-9-]{1,63}\\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,63}\\b"
},
{
name: "Windows file path",
value: "([A-Za-z]):\\\\((?:[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)~]{0,61}\\\\?)*[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)]{0,61})(\\.[A-Za-z\\d]{1,6})?"
},
{
name: "UNIX file path",
value: "(?:/[A-Za-z\\d.][A-Za-z\\d\\-.]{0,61})+"
},
{
name: "MAC address",
value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}"
},
{
name: "Date (yyyy-mm-dd)",
value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])"
},
{
name: "Date (dd/mm/yyyy)",
value: "(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.]((?:19|20)\\d\\d)"
},
{
name: "Date (mm/dd/yyyy)",
value: "(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.]((?:19|20)\\d\\d)"
},
{
name: "Strings",
value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}"
},
],
/**
* @constant
* @default
*/
OUTPUT_FORMAT: ["Highlight matches", "List matches", "List capture groups", "List matches with capture groups"],
/**
* @constant
* @default
*/
DISPLAY_TOTAL: false,
/**
* Regular expression operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
runRegex: function(input, args) {
const userRegex = args[1],
i = args[2],
m = args[3],
s = args[4],
u = args[5],
a = args[6],
displayTotal = args[7],
outputFormat = args[8];
let modifiers = "g";
if (i) modifiers += "i";
if (m) modifiers += "m";
if (s) modifiers += "s";
if (u) modifiers += "u";
if (a) modifiers += "A";
if (userRegex && userRegex !== "^" && userRegex !== "$") {
try {
const regex = new XRegExp(userRegex, modifiers);
switch (outputFormat) {
case "Highlight matches":
return Regex._regexHighlight(input, regex, displayTotal);
case "List matches":
return Utils.escapeHtml(Regex._regexList(input, regex, displayTotal, true, false));
case "List capture groups":
return Utils.escapeHtml(Regex._regexList(input, regex, displayTotal, false, true));
case "List matches with capture groups":
return Utils.escapeHtml(Regex._regexList(input, regex, displayTotal, true, true));
default:
return "Error: Invalid output format";
}
} catch (err) {
return "Invalid regex. Details: " + err.message;
}
} else {
return Utils.escapeHtml(input);
}
},
/**
* @constant
* @default
*/
SEARCH_TYPE: ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"],
/**
* @constant
* @default
*/
FIND_REPLACE_GLOBAL: true,
/**
* @constant
* @default
*/
FIND_REPLACE_CASE: false,
/**
* @constant
* @default
*/
FIND_REPLACE_MULTILINE: true,
/**
* Find / Replace operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runFindReplace: function(input, args) {
let find = args[0].string,
type = args[0].option,
replace = args[1],
g = args[2],
i = args[3],
m = args[4],
modifiers = "";
if (g) modifiers += "g";
if (i) modifiers += "i";
if (m) modifiers += "m";
if (type === "Regex") {
find = new RegExp(find, modifiers);
return input.replace(find, replace);
}
if (type.indexOf("Extended") === 0) {
find = Utils.parseEscapedChars(find);
}
find = new RegExp(Utils.escapeRegex(find), modifiers);
return input.replace(find, replace);
},
/**
* Adds HTML highlights to matches within a string.
*
* @private
* @param {string} input
* @param {RegExp} regex
* @param {boolean} displayTotal
* @returns {string}
*/
_regexHighlight: function(input, regex, displayTotal) {
let output = "",
m,
hl = 1,
i = 0,
total = 0;
while ((m = regex.exec(input))) {
// Moves pointer when an empty string is matched (prevents infinite loop)
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
// Add up to match
output += Utils.escapeHtml(input.slice(i, m.index));
// Add match with highlighting
output += "<span class='hl"+hl+"'>" + Utils.escapeHtml(m[0]) + "</span>";
// Switch highlight
hl = hl === 1 ? 2 : 1;
i = regex.lastIndex;
total++;
}
// Add all after final match
output += Utils.escapeHtml(input.slice(i, input.length));
if (displayTotal)
output = "Total found: " + total + "\n\n" + output;
return output;
},
/**
* Creates a string listing the matches within a string.
*
* @private
* @param {string} input
* @param {RegExp} regex
* @param {boolean} displayTotal
* @param {boolean} matches - Display full match
* @param {boolean} captureGroups - Display each of the capture groups separately
* @returns {string}
*/
_regexList: function(input, regex, displayTotal, matches, captureGroups) {
let output = "",
total = 0,
match;
while ((match = regex.exec(input))) {
// Moves pointer when an empty string is matched (prevents infinite loop)
if (match.index === regex.lastIndex) {
regex.lastIndex++;
}
total++;
if (matches) {
output += match[0] + "\n";
}
if (captureGroups) {
for (let i = 1; i < match.length; i++) {
if (matches) {
output += " Group " + i + ": ";
}
output += match[i] + "\n";
}
}
}
if (displayTotal)
output = "Total found: " + total + "\n\n" + output;
return output.slice(0, -1);
},
};
export default Regex;

View File

@@ -12,128 +12,6 @@ import Utils from "../Utils.js";
*/
const StrUtils = {
/**
* @constant
* @default
*/
REGEX_PRE_POPULATE: [
{
name: "User defined",
value: ""
},
{
name: "IPv4 address",
value: "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?"
},
{
name: "IPv6 address",
value: "((?=.*::)(?!.*::.+::)(::)?([\\dA-Fa-f]{1,4}:(:|\\b)|){5}|([\\dA-Fa-f]{1,4}:){6})((([\\dA-Fa-f]{1,4}((?!\\3)::|:\\b|(?![\\dA-Fa-f])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})"
},
{
name: "Email address",
value: "(\\w[-.\\w]*)@([-\\w]+(?:\\.[-\\w]+)*)\\.([A-Za-z]{2,4})"
},
{
name: "URL",
value: "([A-Za-z]+://)([-\\w]+(?:\\.\\w[-\\w]*)+)(:\\d+)?(/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*)?"
},
{
name: "Domain",
value: "\\b((?=[a-z0-9-]{1,63}\\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,63}\\b"
},
{
name: "Windows file path",
value: "([A-Za-z]):\\\\((?:[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)]{0,61}\\\\?)*[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)]{0,61})(\\.[A-Za-z\\d]{1,6})?"
},
{
name: "UNIX file path",
value: "(?:/[A-Za-z\\d.][A-Za-z\\d\\-.]{0,61})+"
},
{
name: "MAC address",
value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}"
},
{
name: "Date (yyyy-mm-dd)",
value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])"
},
{
name: "Date (dd/mm/yyyy)",
value: "(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.]((?:19|20)\\d\\d)"
},
{
name: "Date (mm/dd/yyyy)",
value: "(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.]((?:19|20)\\d\\d)"
},
{
name: "Strings",
value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}"
},
],
/**
* @constant
* @default
*/
REGEX_CASE_INSENSITIVE: true,
/**
* @constant
* @default
*/
REGEX_MULTILINE_MATCHING: true,
/**
* @constant
* @default
*/
OUTPUT_FORMAT: ["Highlight matches", "List matches", "List capture groups", "List matches with capture groups"],
/**
* @constant
* @default
*/
DISPLAY_TOTAL: false,
/**
* Regular expression operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
runRegex: function(input, args) {
let userRegex = args[1],
i = args[2],
m = args[3],
displayTotal = args[4],
outputFormat = args[5],
modifiers = "g";
if (i) modifiers += "i";
if (m) modifiers += "m";
if (userRegex && userRegex !== "^" && userRegex !== "$") {
try {
const regex = new RegExp(userRegex, modifiers);
switch (outputFormat) {
case "Highlight matches":
return StrUtils._regexHighlight(input, regex, displayTotal);
case "List matches":
return Utils.escapeHtml(StrUtils._regexList(input, regex, displayTotal, true, false));
case "List capture groups":
return Utils.escapeHtml(StrUtils._regexList(input, regex, displayTotal, false, true));
case "List matches with capture groups":
return Utils.escapeHtml(StrUtils._regexList(input, regex, displayTotal, true, true));
default:
return "Error: Invalid output format";
}
} catch (err) {
return "Invalid regex. Details: " + err.message;
}
} else {
return Utils.escapeHtml(input);
}
},
/**
* @constant
* @default
@@ -187,68 +65,28 @@ const StrUtils = {
* @constant
* @default
*/
SEARCH_TYPE: ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"],
SPLIT_DELIM_OPTIONS: [
{name: "Comma", value: ","},
{name: "Space", value: " "},
{name: "Line feed", value: "\\n"},
{name: "CRLF", value: "\\r\\n"},
{name: "Semi-colon", value: ";"},
{name: "Colon", value: ":"},
{name: "Nothing (separate chars)", value: ""}
],
/**
* @constant
* @default
*/
FIND_REPLACE_GLOBAL: true,
/**
* @constant
* @default
*/
FIND_REPLACE_CASE: false,
/**
* @constant
* @default
*/
FIND_REPLACE_MULTILINE: true,
/**
* Find / Replace operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runFindReplace: function(input, args) {
let find = args[0].string,
type = args[0].option,
replace = args[1],
g = args[2],
i = args[3],
m = args[4],
modifiers = "";
if (g) modifiers += "g";
if (i) modifiers += "i";
if (m) modifiers += "m";
if (type === "Regex") {
find = new RegExp(find, modifiers);
return input.replace(find, replace);
}
if (type.indexOf("Extended") === 0) {
find = Utils.parseEscapedChars(find);
}
find = new RegExp(Utils.escapeRegex(find), modifiers);
return input.replace(find, replace);
},
/**
* @constant
* @default
*/
SPLIT_DELIM: ",",
/**
* @constant
* @default
*/
DELIMITER_OPTIONS: ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"],
JOIN_DELIM_OPTIONS: [
{name: "Line feed", value: "\\n"},
{name: "CRLF", value: "\\r\\n"},
{name: "Space", value: " "},
{name: "Comma", value: ","},
{name: "Semi-colon", value: ";"},
{name: "Colon", value: ":"},
{name: "Nothing (join chars)", value: ""}
],
/**
* Split operation.
@@ -258,14 +96,20 @@ const StrUtils = {
* @returns {string}
*/
runSplit: function(input, args) {
let splitDelim = args[0] || StrUtils.SPLIT_DELIM,
joinDelim = Utils.charRep[args[1]],
let splitDelim = args[0],
joinDelim = args[1],
sections = input.split(splitDelim);
return sections.join(joinDelim);
},
/**
* @constant
* @default
*/
DELIMITER_OPTIONS: ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"],
/**
* Filter operation.
*
@@ -510,80 +354,71 @@ const StrUtils = {
/**
* Adds HTML highlights to matches within a string.
*
* @private
* @param {string} input
* @param {RegExp} regex
* @param {boolean} displayTotal
* @returns {string}
* @constant
* @default
*/
_regexHighlight: function(input, regex, displayTotal) {
let output = "",
m,
hl = 1,
i = 0,
total = 0;
while ((m = regex.exec(input))) {
// Add up to match
output += Utils.escapeHtml(input.slice(i, m.index));
// Add match with highlighting
output += "<span class='hl"+hl+"'>" + Utils.escapeHtml(m[0]) + "</span>";
// Switch highlight
hl = hl === 1 ? 2 : 1;
i = regex.lastIndex;
total++;
}
// Add all after final match
output += Utils.escapeHtml(input.slice(i, input.length));
if (displayTotal)
output = "Total found: " + total + "\n\n" + output;
return output;
},
HAMMING_DELIM: "\\n\\n",
/**
* @constant
* @default
*/
HAMMING_INPUT_TYPE: ["Raw string", "Hex"],
/**
* @constant
* @default
*/
HAMMING_UNIT: ["Byte", "Bit"],
/**
* Creates a string listing the matches within a string.
* Hamming Distance operation.
*
* @author GCHQ Contributor [2]
*
* @private
* @param {string} input
* @param {RegExp} regex
* @param {boolean} displayTotal
* @param {boolean} matches - Display full match
* @param {boolean} captureGroups - Display each of the capture groups separately
* @param {Object[]} args
* @returns {string}
*/
_regexList: function(input, regex, displayTotal, matches, captureGroups) {
let output = "",
total = 0,
match;
runHamming: function(input, args) {
const delim = args[0],
byByte = args[1] === "Byte",
inputType = args[2],
samples = input.split(delim);
while ((match = regex.exec(input))) {
total++;
if (matches) {
output += match[0] + "\n";
}
if (captureGroups) {
for (let i = 1; i < match.length; i++) {
if (matches) {
output += " Group " + i + ": ";
}
output += match[i] + "\n";
if (samples.length !== 2) {
return "Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter.";
}
if (samples[0].length !== samples[1].length) {
return "Error: Both inputs must be of the same length.";
}
if (inputType === "Hex") {
samples[0] = Utils.fromHex(samples[0]);
samples[1] = Utils.fromHex(samples[1]);
} else {
samples[0] = Utils.strToByteArray(samples[0]);
samples[1] = Utils.strToByteArray(samples[1]);
}
let dist = 0;
for (let i = 0; i < samples[0].length; i++) {
const lhs = samples[0][i],
rhs = samples[1][i];
if (byByte && lhs !== rhs) {
dist++;
} else if (!byByte) {
let xord = lhs ^ rhs;
while (xord) {
dist++;
xord &= xord - 1;
}
}
}
if (displayTotal)
output = "Total found: " + total + "\n\n" + output;
return output;
return dist.toString();
},
};

View File

@@ -101,32 +101,39 @@ const Tidy = {
/**
* Drop bytes operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
* @returns {ArrayBuffer}
*/
runDropBytes: function(input, args) {
let start = args[0],
const start = args[0],
length = args[1],
applyToEachLine = args[2];
if (start < 0 || length < 0)
throw "Error: Invalid value";
if (!applyToEachLine)
return input.slice(0, start).concat(input.slice(start+length, input.length));
if (!applyToEachLine) {
const left = input.slice(0, start),
right = input.slice(start + length, input.byteLength);
let result = new Uint8Array(left.byteLength + right.byteLength);
result.set(new Uint8Array(left), 0);
result.set(new Uint8Array(right), left.byteLength);
return result.buffer;
}
// Split input into lines
const data = new Uint8Array(input);
let lines = [],
line = [],
i;
for (i = 0; i < input.length; i++) {
if (input[i] === 0x0a) {
for (i = 0; i < data.length; i++) {
if (data[i] === 0x0a) {
lines.push(line);
line = [];
} else {
line.push(input[i]);
line.push(data[i]);
}
}
lines.push(line);
@@ -136,7 +143,7 @@ const Tidy = {
output = output.concat(lines[i].slice(0, start).concat(lines[i].slice(start+length, lines[i].length)));
output.push(0x0a);
}
return output.slice(0, output.length-1);
return new Uint8Array(output.slice(0, output.length-1)).buffer;
},
@@ -154,12 +161,12 @@ const Tidy = {
/**
* Take bytes operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
* @returns {ArrayBuffer}
*/
runTakeBytes: function(input, args) {
let start = args[0],
const start = args[0],
length = args[1],
applyToEachLine = args[2];
@@ -170,16 +177,17 @@ const Tidy = {
return input.slice(start, start+length);
// Split input into lines
const data = new Uint8Array(input);
let lines = [],
line = [];
let i;
line = [],
i;
for (i = 0; i < input.length; i++) {
if (input[i] === 0x0a) {
for (i = 0; i < data.length; i++) {
if (data[i] === 0x0a) {
lines.push(line);
line = [];
} else {
line.push(input[i]);
line.push(data[i]);
}
}
lines.push(line);
@@ -189,7 +197,7 @@ const Tidy = {
output = output.concat(lines[i].slice(start, start+length));
output.push(0x0a);
}
return output.slice(0, output.length-1);
return new Uint8Array(output.slice(0, output.length-1)).buffer;
},

26
src/core/operations/XKCD.js Executable file
View File

@@ -0,0 +1,26 @@
/**
* XKCD operations.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*
* @namespace
*/
const XKCD = {
/**
* XKCD Random Number operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {number}
*/
runRandomNumber: function(input, args) {
return 4; // chosen by fair dice roll.
// guaranteed to be random.
},
};
export default XKCD;

View File

@@ -49,9 +49,11 @@ App.prototype.setup = function() {
this.manager.setup();
this.resetLayout();
this.setCompileMessage();
this.loadURIParams();
log.debug("App loaded");
this.appLoaded = true;
this.loadURIParams();
this.loaded();
};
@@ -91,7 +93,7 @@ App.prototype.loaded = function() {
* @param {boolean} [logToConsole=false]
*/
App.prototype.handleError = function(err, logToConsole) {
if (logToConsole) console.error(err);
if (logToConsole) log.error(err);
const msg = err.displayStr || err.toString();
this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors);
};
@@ -129,6 +131,7 @@ App.prototype.autoBake = function() {
if (this.autoBakePause) return false;
if (this.autoBake_ && !this.baking) {
log.debug("Auto-baking");
this.bake();
} else {
this.manager.controls.showStaleIndicator();
@@ -440,6 +443,7 @@ App.prototype.getRecipeConfig = function() {
/**
* Given a recipe configuration, sets the recipe to that configuration.
*
* @fires Manager#statechange
* @param {Object[]} recipeConfig - The recipe configuration
*/
App.prototype.setRecipeConfig = function(recipeConfig) {
@@ -569,7 +573,7 @@ App.prototype.isLocalStorageAvailable = function() {
App.prototype.alert = function(str, style, timeout, silent) {
const time = new Date();
console.log("[" + time.toLocaleString() + "] " + str);
log.info("[" + time.toLocaleString() + "] " + str);
if (silent) return;
style = style || "danger";

View File

@@ -110,6 +110,15 @@ HTMLOperation.prototype.highlightSearchString = function(searchStr, namePos, des
}
if (this.description && descPos >= 0) {
// Find HTML tag offsets
const re = /<[^>]+>/g;
let match;
while ((match = re.exec(this.description))) {
// If the search string occurs within an HTML tag, return without highlighting it.
if (descPos >= match.index && descPos <= (match.index + match[0].length))
return;
}
this.description = this.description.slice(0, descPos) + "<b><u>" +
this.description.slice(descPos, descPos + searchStr.length) + "</u></b>" +
this.description.slice(descPos + searchStr.length);

View File

@@ -1,4 +1,5 @@
import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker.js";
import Utils from "../core/Utils.js";
/**
@@ -61,9 +62,14 @@ InputWaiter.prototype.set = function(input) {
if (input instanceof File) {
this.setFile(input);
inputText.value = "";
this.setInputInfo(input.size, null);
} else {
inputText.value = input;
this.closeFile();
window.dispatchEvent(this.manager.statechange);
const lines = input.length < (this.app.options.ioDisplayThreshold * 1024) ?
input.count("\n") + 1 : null;
this.setInputInfo(input.length, lines);
}
};
@@ -81,6 +87,7 @@ InputWaiter.prototype.setFile = function(file) {
fileType = document.getElementById("input-file-type"),
fileLoaded = document.getElementById("input-file-loaded");
this.fileBuffer = new ArrayBuffer();
fileOverlay.style.display = "block";
fileName.textContent = file.name;
fileSize.textContent = file.size.toLocaleString() + " bytes";
@@ -100,21 +107,28 @@ InputWaiter.prototype.setInputInfo = function(length, lines) {
width = width < 2 ? 2 : width;
const lengthStr = length.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
let msg = "length: " + lengthStr;
document.getElementById("input-info").innerHTML = "length: " + lengthStr + "<br>lines: " + linesStr;
if (typeof lines === "number") {
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
msg += "<br>lines: " + linesStr;
}
document.getElementById("input-info").innerHTML = msg;
};
/**
* Handler for input scroll events.
* Scrolls the highlighter pane to match the input textarea position and updates history state.
* Handler for input change events.
*
* @param {event} e
*
* @fires Manager#statechange
*/
InputWaiter.prototype.inputChange = function(e) {
// Ignore this function if the input is a File
if (this.fileBuffer) return;
// Remove highlighting from input and output panes as the offsets might be different now
this.manager.highlighter.removeHighlights();
@@ -123,18 +137,47 @@ InputWaiter.prototype.inputChange = function(e) {
// Update the input metadata info
const inputText = this.get();
const lines = inputText.count("\n") + 1;
const lines = inputText.length < (this.app.options.ioDisplayThreshold * 1024) ?
inputText.count("\n") + 1 : null;
this.setInputInfo(inputText.length, lines);
if (this.badKeys.indexOf(e.keyCode) < 0) {
if (e && this.badKeys.indexOf(e.keyCode) < 0) {
// Fire the statechange event as the input has been modified
window.dispatchEvent(this.manager.statechange);
}
};
/**
* Handler for input paste events.
* Checks that the size of the input is below the display limit, otherwise treats it as a file/blob.
*
* @param {event} e
*/
InputWaiter.prototype.inputPaste = function(e) {
const pastedData = e.clipboardData.getData("Text");
if (pastedData.length < (this.app.options.ioDisplayThreshold * 1024)) {
this.inputChange(e);
} else {
e.preventDefault();
e.stopPropagation();
const file = new File([pastedData], "PastedData", {
type: "text/plain",
lastModified: Date.now()
});
this.loaderWorker = new LoaderWorker();
this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
this.loaderWorker.postMessage({"file": file});
this.set(file);
return false;
}
};
/**
* Handler for input dragover events.
* Gives the user a visual cue to show that items can be dropped here.
@@ -214,13 +257,35 @@ InputWaiter.prototype.handleLoaderMessage = function(e) {
fileLoaded.textContent = r.progress + "%";
}
if (r.hasOwnProperty("error")) {
this.app.alert(r.error, "danger", 10000);
}
if (r.hasOwnProperty("fileBuffer")) {
log.debug("Input file loaded");
this.fileBuffer = r.fileBuffer;
this.displayFilePreview();
window.dispatchEvent(this.manager.statechange);
}
};
/**
* Shows a chunk of the file in the input behind the file overlay.
*/
InputWaiter.prototype.displayFilePreview = function() {
const inputText = document.getElementById("input-text"),
fileSlice = this.fileBuffer.slice(0, 4096);
inputText.style.overflow = "hidden";
inputText.classList.add("blur");
inputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice));
if (this.fileBuffer.byteLength > 4096) {
inputText.value += "[truncated]...";
}
};
/**
* Handler for file close events.
*/
@@ -228,6 +293,9 @@ InputWaiter.prototype.closeFile = function() {
if (this.loaderWorker) this.loaderWorker.terminate();
this.fileBuffer = null;
document.getElementById("input-file").style.display = "none";
const inputText = document.getElementById("input-text");
inputText.style.overflow = "auto";
inputText.classList.remove("blur");
};

View File

@@ -20,7 +20,7 @@ self.addEventListener("message", function(e) {
/**
* Loads a file object into an ArrayBuffer, then transfers it back to the parent thread.
*
*
* @param {File} file
*/
self.loadFile = function(file) {
@@ -46,5 +46,9 @@ self.loadFile = function(file) {
seek();
};
reader.onerror = function(e) {
self.postMessage({"error": file.error.message});
};
seek();
};

View File

@@ -58,7 +58,7 @@ const Manager = function(app) {
this.ops = new OperationsWaiter(this.app, this);
this.input = new InputWaiter(this.app, this);
this.output = new OutputWaiter(this.app, this);
this.options = new OptionsWaiter(this.app);
this.options = new OptionsWaiter(this.app, this);
this.highlighter = new HighlighterWaiter(this.app, this);
this.seasonal = new SeasonalWaiter(this.app, this);
this.bindings = new BindingsWaiter(this.app, this);
@@ -119,7 +119,7 @@ Manager.prototype.initialiseEventListeners = function() {
this.addDynamicListener(".op-list .op-icon", "mouseover", this.ops.opIconMouseover, this.ops);
this.addDynamicListener(".op-list .op-icon", "mouseleave", this.ops.opIconMouseleave, this.ops);
this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops);
this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd.bind(this.recipe));
this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe);
// Recipe
this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe);
@@ -132,7 +132,8 @@ Manager.prototype.initialiseEventListeners = function() {
this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe));
// Input
this.addMultiEventListener("#input-text", "keyup paste", this.input.inputChange, this.input);
this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input);
this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input);
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input));
this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input);
@@ -172,6 +173,7 @@ Manager.prototype.initialiseEventListeners = function() {
this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options);
this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options);
document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options));
document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options));
// Misc
window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings));

View File

@@ -167,7 +167,8 @@ OperationsWaiter.prototype.opListCreate = function(e) {
OperationsWaiter.prototype.enableOpsListPopovers = function(el) {
$(el).find("[data-toggle=popover]").addBack("[data-toggle=popover]")
.popover({trigger: "manual"})
.on("mouseenter", function() {
.on("mouseenter", function(e) {
if (e.buttons > 0) return; // Mouse button held down - likely dragging an opertion
const _this = this;
$(this).popover("show");
$(".popover").on("mouseleave", function () {
@@ -178,7 +179,7 @@ OperationsWaiter.prototype.enableOpsListPopovers = function(el) {
setTimeout(function() {
// Determine if the popover associated with this element is being hovered over
if ($(_this).data("bs.popover") &&
!$(_this).data("bs.popover").$tip.is(":hover")) {
($(_this).data("bs.popover").$tip && !$(_this).data("bs.popover").$tip.is(":hover"))) {
$(_this).popover("hide");
}
}, 50);

View File

@@ -8,8 +8,9 @@
* @constructor
* @param {App} app - The main view object for CyberChef.
*/
const OptionsWaiter = function(app) {
const OptionsWaiter = function(app, manager) {
this.app = app;
this.manager = manager;
};
@@ -86,6 +87,7 @@ OptionsWaiter.prototype.switchChange = function(e, state) {
const el = e.target;
const option = el.getAttribute("option");
log.debug(`Setting ${option} to ${state}`);
this.app.options[option] = state;
if (this.app.isLocalStorageAvailable())
@@ -102,8 +104,10 @@ OptionsWaiter.prototype.switchChange = function(e, state) {
OptionsWaiter.prototype.numberChange = function(e) {
const el = e.target;
const option = el.getAttribute("option");
const val = parseInt(el.value, 10);
this.app.options[option] = parseInt(el.value, 10);
log.debug(`Setting ${option} to ${val}`);
this.app.options[option] = val;
if (this.app.isLocalStorageAvailable())
localStorage.setItem("options", JSON.stringify(this.app.options));
@@ -120,6 +124,7 @@ OptionsWaiter.prototype.selectChange = function(e) {
const el = e.target;
const option = el.getAttribute("option");
log.debug(`Setting ${option} to ${el.value}`);
this.app.options[option] = el.value;
if (this.app.isLocalStorageAvailable())
@@ -149,6 +154,8 @@ OptionsWaiter.prototype.setWordWrap = function() {
/**
* Changes the theme by setting the class of the <html> element.
*
* @param {Event} e
*/
OptionsWaiter.prototype.themeChange = function (e) {
const themeClass = e.target.value;
@@ -156,4 +163,16 @@ OptionsWaiter.prototype.themeChange = function (e) {
document.querySelector(":root").className = themeClass;
};
/**
* Changes the console logging level.
*
* @param {Event} e
*/
OptionsWaiter.prototype.logLevelChange = function (e) {
const level = e.target.value;
log.setLevel(level, false);
this.manager.worker.setLogLevel();
};
export default OptionsWaiter;

View File

@@ -41,6 +41,7 @@ OutputWaiter.prototype.get = function() {
* @param {boolean} [preserveBuffer=false] - Whether to preserve the dishBuffer
*/
OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
log.debug("Output type: " + type);
const outputText = document.getElementById("output-text");
const outputHtml = document.getElementById("output-html");
const outputFile = document.getElementById("output-file");
@@ -63,7 +64,7 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
outputText.value = "";
outputHtml.innerHTML = data;
this.dishStr = Utils.stripHtmlTags(data, true);
this.dishStr = Utils.unescapeHtml(Utils.stripHtmlTags(data, true));
length = data.length;
lines = this.dishStr.count("\n") + 1;
@@ -73,7 +74,7 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
try {
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
} catch (err) {
console.error(err);
log.error(err);
}
}
break;
@@ -127,6 +128,13 @@ OutputWaiter.prototype.setFile = function(buf) {
fileOverlay.style.display = "block";
fileSize.textContent = file.size.toLocaleString() + " bytes";
// Display preview slice in the background
const outputText = document.getElementById("output-text"),
fileSlice = this.dishBuffer.slice(0, 4096);
outputText.classList.add("blur");
outputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice));
};
@@ -136,6 +144,7 @@ OutputWaiter.prototype.setFile = function(buf) {
OutputWaiter.prototype.closeFile = function() {
this.dishBuffer = null;
document.getElementById("output-file").style.display = "none";
document.getElementById("output-text").classList.remove("blur");
};
@@ -162,6 +171,7 @@ OutputWaiter.prototype.displayFileSlice = function() {
sliceTo = parseInt(sliceToEl.value, 10),
str = Utils.arrayBufferToStr(this.dishBuffer.slice(sliceFrom, sliceTo));
document.getElementById("output-text").classList.remove("blur");
showFileOverlay.style.display = "block";
this.set(str, "string", new Date().getTime() - startTime, true);
};
@@ -169,13 +179,14 @@ OutputWaiter.prototype.displayFileSlice = function() {
/**
* Handler for show file overlay events.
*
*
* @param {Event} e
*/
OutputWaiter.prototype.showFileOverlayClick = function(e) {
const outputFile = document.getElementById("output-file"),
showFileOverlay = e.target;
document.getElementById("output-text").classList.add("blur");
outputFile.style.display = "block";
showFileOverlay.style.display = "none";
this.setOutputInfo(this.dishBuffer.byteLength, null, 0);

View File

@@ -253,7 +253,7 @@ RecipeWaiter.prototype.breakpointClick = function(e) {
*/
RecipeWaiter.prototype.operationDblclick = function(e) {
e.target.remove();
window.dispatchEvent(this.manager.statechange);
this.opRemove(e);
};
@@ -266,7 +266,7 @@ RecipeWaiter.prototype.operationDblclick = function(e) {
*/
RecipeWaiter.prototype.operationChildDblclick = function(e) {
e.target.parentNode.remove();
window.dispatchEvent(this.manager.statechange);
this.opRemove(e);
};
@@ -421,6 +421,7 @@ RecipeWaiter.prototype.dropdownToggleClick = function(e) {
* @param {event} e
*/
RecipeWaiter.prototype.opAdd = function(e) {
log.debug(`'${e.target.querySelector(".arg-title").textContent}' added to recipe`);
window.dispatchEvent(this.manager.statechange);
};
@@ -433,6 +434,7 @@ RecipeWaiter.prototype.opAdd = function(e) {
* @param {event} e
*/
RecipeWaiter.prototype.opRemove = function(e) {
log.debug("Operation removed from recipe");
window.dispatchEvent(this.manager.statechange);
};

View File

@@ -21,8 +21,10 @@ const WorkerWaiter = function(app, manager) {
* Sets up the ChefWorker and associated listeners.
*/
WorkerWaiter.prototype.registerChefWorker = function() {
log.debug("Registering new ChefWorker");
this.chefWorker = new ChefWorker();
this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this));
this.setLogLevel();
let docURL = document.location.href.split(/[#?]/)[0];
const index = docURL.lastIndexOf("/");
@@ -40,8 +42,10 @@ WorkerWaiter.prototype.registerChefWorker = function() {
*/
WorkerWaiter.prototype.handleChefMessage = function(e) {
const r = e.data;
log.debug("Receiving '" + r.action + "' from ChefWorker");
switch (r.action) {
case "bakeSuccess":
case "bakeComplete":
this.bakingComplete(r.data);
break;
case "bakeError":
@@ -52,12 +56,14 @@ WorkerWaiter.prototype.handleChefMessage = function(e) {
break;
case "workerLoaded":
this.app.workerLoaded = true;
log.debug("ChefWorker loaded");
this.app.loaded();
break;
case "statusMessage":
this.manager.output.setStatusMsg(r.data);
break;
case "optionUpdate":
log.debug(`Setting ${r.data.option} to ${r.data.value}`);
this.app.options[r.data.option] = r.data.value;
break;
case "setRegisters":
@@ -67,7 +73,7 @@ WorkerWaiter.prototype.handleChefMessage = function(e) {
this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction);
break;
default:
console.error("Unrecognised message from ChefWorker", e);
log.error("Unrecognised message from ChefWorker", e);
break;
}
};
@@ -113,6 +119,7 @@ WorkerWaiter.prototype.bakingComplete = function(response) {
this.app.progress = response.progress;
this.manager.recipe.updateBreakpointIndicator(response.progress);
this.manager.output.set(response.result, response.type, response.duration);
log.debug("--- Bake complete ---");
};
@@ -145,7 +152,7 @@ WorkerWaiter.prototype.bake = function(input, recipeConfig, options, progress, s
* Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant
* JavaScript code needed to do a real bake.
*
* @param {Objectp[]} [recipeConfig]
* @param {Object[]} [recipeConfig]
*/
WorkerWaiter.prototype.silentBake = function(recipeConfig) {
this.chefWorker.postMessage({
@@ -178,4 +185,19 @@ WorkerWaiter.prototype.highlight = function(recipeConfig, direction, pos) {
};
/**
* Sets the console log level in the worker.
*
* @param {string} level
*/
WorkerWaiter.prototype.setLogLevel = function(level) {
if (!this.chefWorker) return;
this.chefWorker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
};
export default WorkerWaiter;

View File

@@ -59,6 +59,9 @@
"for i in range(additional): Pylon()",
"(creating unresolved tension...",
"Symlinking emacs and vim to ed...",
"Training branch predictor...",
"Timing cache hits...",
"Speculatively executing recipes..."
];
// Shuffle array using Durstenfeld algorithm
@@ -115,7 +118,7 @@
<span id="notice">
<script type="text/javascript">
// Must be text/javascript rather than application/javascript otherwise IE won't recognise it...
if (navigator.userAgent && navigator.userAgent.match(/MSIE \d\d?\./)) {
if (navigator.userAgent && navigator.userAgent.match(/Trident/)) {
document.write("Internet Explorer is not supported, please use Firefox or Chrome instead");
alert("Internet Explorer is not supported, please use Firefox or Chrome instead");
}
@@ -180,8 +183,9 @@
</div>
<div class="textarea-wrapper no-select">
<div id="input-highlighter" class="no-select"></div>
<textarea id="input-text"></textarea>
<textarea id="input-text" spellcheck="false"></textarea>
<div id="input-file">
<div class="file-overlay"></div>
<div style="position: relative; height: 100%;">
<div class="card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/>
@@ -215,9 +219,10 @@
<div class="textarea-wrapper">
<div id="output-highlighter" class="no-select"></div>
<div id="output-html"></div>
<textarea id="output-text" readonly="readonly"></textarea>
<textarea id="output-text" readonly="readonly" spellcheck="false"></textarea>
<img id="show-file-overlay" aria-hidden="true" src="<%- require('../static/images/file-32x32.png') %>" alt="Show file overlay" title="Show file overlay"/>
<div id="output-file">
<div class="file-overlay"></div>
<div style="position: relative; height: 100%;">
<div class="card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/>
@@ -230,7 +235,7 @@
</span>
<input type="number" class="form-control" id="output-file-slice-from" placeholder="From" value="0" step="1024" min="0">
<div class="input-group-addon">to</div>
<input type="number" class="form-control" id="output-file-slice-to" placeholder="To" value="1024" step="1024" min="0">
<input type="number" class="form-control" id="output-file-slice-to" placeholder="To" value="2048" step="1024" min="0">
</div>
</div>
</div>
@@ -369,8 +374,19 @@
<label for="errorTimeout"> Operation error timeout in ms (0 for never)</label>
</div>
<div class="option-item">
<input type="number" option="outputFileThreshold" id="outputFileThreshold" />
<label for="outputFileThreshold"> Size threshold for treating the output as a file (KiB)</label>
<input type="number" option="ioDisplayThreshold" id="ioDisplayThreshold" />
<label for="ioDisplayThreshold"> Size threshold for treating the input and output as a file (KiB)</label>
</div>
<div class="option-item">
<select option="logLevel" id="logLevel">
<option value="silent">Silent</option>
<option value="error">Error</option>
<option value="warn">Warn</option>
<option value="info">Info</option>
<option value="debug">Debug</option>
<option value="trace">Trace</option>
</select>
<label for="logLevel"> Console logging level</label>
</div>
</div>
<div class="modal-footer">

View File

@@ -47,7 +47,8 @@ function main() {
attemptHighlight: true,
theme: "classic",
useMetaKey: false,
outputFileThreshold: 1024
ioDisplayThreshold: 512,
logLevel: "info"
};
document.removeEventListener("DOMContentLoaded", main, false);
@@ -55,9 +56,6 @@ function main() {
window.app.setup();
}
// Fix issues with browsers that don't support console.log()
window.console = console || {log: function() {}, error: function() {}};
window.compileTime = moment.tz(COMPILE_TIME, "DD/MM/YYYY HH:mm:ss z", "UTC").valueOf();
window.compileMessage = COMPILE_MSG;

View File

@@ -7,7 +7,7 @@
*/
/* Libraries */
import "google-code-prettify/src/prettify.css";
import "highlight.js/styles/vs.css";
/* Frameworks */
import "./vendors/bootstrap.less";

View File

@@ -84,10 +84,17 @@
top: 0;
width: 100%;
height: 100%;
background-color: var(--title-background-colour);
display: none;
}
.file-overlay {
position: absolute;
opacity: 0.8;
background-color: var(--title-background-colour);
width: 100%;
height: 100%;
}
#show-file-overlay {
position: absolute;
right: 15px;

View File

@@ -1,3 +1,5 @@
/* eslint no-console: 0 */
/**
* TestRunner.js
*
@@ -28,6 +30,7 @@ import "./tests/operations/MS.js";
import "./tests/operations/PHP.js";
import "./tests/operations/NetBIOS.js";
import "./tests/operations/OTP.js";
import "./tests/operations/Regex.js";
import "./tests/operations/StrUtils.js";
import "./tests/operations/SeqUtils.js";

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@ TestRegister.addTests([
},
{
name: "Fork, (expect) Error, Merge",
input: "1\n2\na\n4",
input: "1.1\n2.5\na\n3.4",
expectedError: true,
recipeConfig: [
{
@@ -45,8 +45,8 @@ TestRegister.addTests([
args: ["\n", "\n", false],
},
{
op: "To Base",
args: [16],
op: "Object Identifier to Hex",
args: [],
},
{
op: "Merge",

View File

@@ -0,0 +1,59 @@
/**
* StrUtils tests.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister.js";
TestRegister.addTests([
{
name: "Regex: non-HTML op",
input: "/<>",
expectedOutput: "/<>",
recipeConfig: [
{
"op": "Regular expression",
"args": ["User defined", "", true, true, false, false, false, false, "Highlight matches"]
},
{
"op": "Remove whitespace",
"args": [true, true, true, true, true, false]
}
],
},
{
name: "Regex: Dot matches all",
input: "Hello\nWorld",
expectedOutput: "Hello\nWorld",
recipeConfig: [
{
"op": "Regular expression",
"args": ["User defined", ".+", true, true, true, false, false, false, "List matches"]
}
],
},
{
name: "Regex: Astral off",
input: "𝌆😆",
expectedOutput: "",
recipeConfig: [
{
"op": "Regular expression",
"args": ["User defined", "\\pS", true, true, false, false, false, false, "List matches"]
}
],
},
{
name: "Regex: Astral on",
input: "𝌆😆",
expectedOutput: "𝌆\n😆",
recipeConfig: [
{
"op": "Regular expression",
"args": ["User defined", "\\pS", true, true, false, false, true, false, "List matches"]
}
],
}
]);

View File

@@ -8,21 +8,6 @@
import TestRegister from "../../TestRegister.js";
TestRegister.addTests([
{
name: "Regex, non-HTML op",
input: "/<>",
expectedOutput: "/<>",
recipeConfig: [
{
"op": "Regular expression",
"args": ["User defined", "", true, true, false, "Highlight matches"]
},
{
"op": "Remove whitespace",
"args": [true, true, true, true, true, false]
}
],
},
{
name: "Diff, basic usage",
input: "testing23\n\ntesting123",

View File

@@ -35,7 +35,8 @@ module.exports = {
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
moment: "moment-timezone"
moment: "moment-timezone",
log: "loglevel"
}),
new webpack.BannerPlugin({
banner: banner,