Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"worker-loader": "^3.0.8"
},
"dependencies": {
"@alexaltea/capstone-js": "^3.0.5",
"@astronautlabs/amf": "^0.0.6",
"@babel/polyfill": "^7.12.1",
"@blu3r4y/lzma": "^2.3.3",
Expand Down
1 change: 1 addition & 0 deletions src/core/config/Categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@
"Chi Square",
"P-list Viewer",
"Disassemble x86",
"Disassemble ARM",
"Pseudo-Random Number Generator",
"Generate De Bruijn Sequence",
"Generate UUID",
Expand Down
193 changes: 193 additions & 0 deletions src/core/operations/DisassembleARM.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/**
* @author MedjedThomasXM
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/

import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { isWorkerEnvironment } from "../Utils.mjs";
import cs from "../vendor/capstone.min.js";

/**
* Disassemble ARM operation
*/
class DisassembleARM extends Operation {

/**
* DisassembleARM constructor
*/
constructor() {
super();

this.name = "Disassemble ARM";
this.module = "Shellcode";
this.description = "Disassembles ARM machine code into assembly language.<br><br>Supports ARM (32-bit), Thumb, and ARM64 (AArch64) architectures using the Capstone disassembly framework.<br><br>Input should be in hexadecimal.";
this.infoURL = "https://wikipedia.org/wiki/ARM_architecture_family";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Architecture",
"type": "option",
"value": ["ARM (32-bit)", "ARM64 (AArch64)"]
},
{
"name": "Mode",
"type": "option",
"value": ["ARM", "Thumb", "Thumb + Cortex-M", "ARMv8"]
},
{
"name": "Endianness",
"type": "option",
"value": ["Little Endian", "Big Endian"]
},
{
"name": "Starting address (hex)",
"type": "number",
"value": 0
},
{
"name": "Show instruction hex",
"type": "boolean",
"value": true
},
{
"name": "Show instruction position",
"type": "boolean",
"value": true
}
];
}

/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
async run(input, args) {
const [
architecture,
mode,
endianness,
startAddress,
showHex,
showPosition
] = args;

// Remove whitespace from input
const hexInput = input.replace(/\s/g, "");

// Validate hex input
if (!/^[0-9a-fA-F]*$/.test(hexInput)) {
throw new OperationError("Invalid hexadecimal input. Please provide valid hex characters only.");
}

if (hexInput.length === 0) {
return "";
}

if (hexInput.length % 2 !== 0) {
throw new OperationError("Invalid hexadecimal input. Length must be even.");
}

// Convert hex string to byte array
const bytes = [];
for (let i = 0; i < hexInput.length; i += 2) {
bytes.push(parseInt(hexInput.substr(i, 2), 16));
}

// Determine architecture constant
let arch;
if (architecture === "ARM64 (AArch64)") {
arch = cs.ARCH_ARM64;
} else {
arch = cs.ARCH_ARM;
}

// Determine mode constant
let modeValue = cs.MODE_LITTLE_ENDIAN;

if (architecture === "ARM (32-bit)") {
switch (mode) {
case "ARM":
modeValue = cs.MODE_ARM;
break;
case "Thumb":
modeValue = cs.MODE_THUMB;
break;
case "Thumb + Cortex-M":
modeValue = cs.MODE_THUMB | cs.MODE_MCLASS;
break;
case "ARMv8":
modeValue = cs.MODE_ARM | cs.MODE_V8;
break;
default:
modeValue = cs.MODE_ARM;
}
} else {
// ARM64 only has one mode (ARM mode is default for ARM64)
modeValue = cs.MODE_ARM;
}

// Add endianness
if (endianness === "Big Endian") {
modeValue |= cs.MODE_BIG_ENDIAN;
}

if (isWorkerEnvironment()) {
self.sendStatusMessage("Disassembling...");
}

let disassembler;
try {
disassembler = new cs.Capstone(arch, modeValue);
} catch (e) {
throw new OperationError(`Failed to initialise Capstone disassembler: ${e}`);
}

let instructions;
try {
instructions = disassembler.disasm(bytes, startAddress);
} catch (e) {
disassembler.close();
// Check if it's a "no valid instructions" error (code 0 means OK but nothing decoded)
if (e && e.includes && e.includes("code 0:")) {
throw new OperationError(`No valid ${architecture} instructions found in input. The bytes may be for a different architecture or mode.`);
}
throw new OperationError(`Disassembly failed: ${e}`);
}

// Format output
const output = [];
for (const insn of instructions) {
let line = "";

if (showPosition) {
// Format address as hex with 0x prefix
const addrHex = "0x" + insn.address.toString(16).padStart(8, "0");
line += addrHex + " ";
}

if (showHex) {
// Format instruction bytes as hex
const bytesHex = insn.bytes.map(b => b.toString(16).padStart(2, "0")).join("");
line += bytesHex.padEnd(16, " ") + " ";
}

line += insn.mnemonic;
if (insn.op_str) {
line += " " + insn.op_str;
}

output.push(line);
}

disassembler.close();

return output.join("\n");
}

}

export default DisassembleARM;
7,965 changes: 7,965 additions & 0 deletions src/core/vendor/capstone.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tests/operations/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import "./tests/Crypt.mjs";
import "./tests/CSV.mjs";
import "./tests/DateTime.mjs";
import "./tests/DefangIP.mjs";
import "./tests/DisassembleARM.mjs";
import "./tests/DropNthBytes.mjs";
import "./tests/ECDSA.mjs";
import "./tests/ELFInfo.mjs";
Expand Down
Loading
Loading