This commit is contained in:
Thomas M 2026-01-30 12:39:12 +01:00 committed by GitHub
commit 86500dfc82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 8544 additions and 0 deletions

6
package-lock.json generated
View file

@ -10,6 +10,7 @@
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@alexaltea/capstone-js": "^3.0.5",
"@astronautlabs/amf": "^0.0.6",
"@babel/polyfill": "^7.12.1",
"@blu3r4y/lzma": "^2.3.3",
@ -161,6 +162,11 @@
"worker-loader": "^3.0.8"
}
},
"node_modules/@alexaltea/capstone-js": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@alexaltea/capstone-js/-/capstone-js-3.0.5.tgz",
"integrity": "sha512-HWa4d5vblYc3OEJ9MpcXFo0gV/oDLTI5iH7ng80Gs3/Wo3lcYvB14gDDwSr9So1F+fuwIET8meo6TxTezEyqTg=="
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",

View file

@ -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",

View file

@ -548,6 +548,7 @@
"Chi Square",
"P-list Viewer",
"Disassemble x86",
"Disassemble ARM",
"Pseudo-Random Number Generator",
"Generate De Bruijn Sequence",
"Generate UUID",

View file

@ -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;

7965
src/core/vendor/capstone.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -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";

View file

@ -0,0 +1,377 @@
/**
* Disassemble ARM tests.
*
* @author MedjedThomasXM
*
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
// ==================== ARM32 TESTS ====================
{
name: "Disassemble ARM: ARM32 NOP (mov r0, r0)",
input: "00 00 a0 e1",
expectedMatch: /mov\s+r0,\s*r0/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM32 bx lr",
input: "1e ff 2f e1",
expectedMatch: /bx\s+lr/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM32 push {fp, lr}",
input: "00 48 2d e9",
expectedMatch: /push\s+\{fp,\s*lr\}/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM32 add fp, sp, #4",
input: "04 b0 8d e2",
expectedMatch: /add\s+fp,\s*sp/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM32 ldr r0, [r1]",
input: "00 00 91 e5",
expectedMatch: /ldr\s+r0,\s*\[r1\]/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM32 str r0, [r1]",
input: "00 00 81 e5",
expectedMatch: /str\s+r0,\s*\[r1\]/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM32 bl (branch link)",
input: "00 00 00 eb",
expectedMatch: /bl\s+/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM32 mul r0, r1, r2",
input: "91 02 00 e0",
expectedMatch: /mul\s+r0,\s*r1,\s*r2/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true],
},
],
},
// ==================== ARM32 THUMB TESTS ====================
{
name: "Disassemble ARM: Thumb mov r0, r0",
input: "00 46",
expectedMatch: /mov\s+r0,\s*r0/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "Thumb", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: Thumb bx lr",
input: "70 47",
expectedMatch: /bx\s+lr/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "Thumb", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: Thumb push {r4, lr}",
input: "10 b5",
expectedMatch: /push\s+\{r4,\s*lr\}/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "Thumb", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: Thumb pop {r4, pc}",
input: "10 bd",
expectedMatch: /pop\s+\{r4,\s*pc\}/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "Thumb", "Little Endian", 0, true, true],
},
],
},
// ==================== ARM64 TESTS ====================
{
name: "Disassemble ARM: ARM64 ret",
input: "c0 03 5f d6",
expectedMatch: /ret/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 mov x0, #0",
input: "00 00 80 d2",
expectedMatch: /mov[z]?\s+x0,\s*#0/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 stp x29, x30, [sp, #-16]!",
input: "fd 7b bf a9",
expectedMatch: /stp\s+x29,\s*x30,\s*\[sp/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 ldp x29, x30, [sp], #16",
input: "fd 7b c1 a8",
expectedMatch: /ldp\s+x29,\s*x30,\s*\[sp\]/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 add x0, x1, x2",
input: "20 00 02 8b",
expectedMatch: /add\s+x0,\s*x1,\s*x2/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 sub x0, x1, x2",
input: "20 00 02 cb",
expectedMatch: /sub\s+x0,\s*x1,\s*x2/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 mul x0, x1, x2",
input: "20 7c 02 9b",
expectedMatch: /mul\s+x0,\s*x1,\s*x2/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 ldr x0, [x1]",
input: "20 00 40 f9",
expectedMatch: /ldr\s+x0,\s*\[x1\]/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 str x0, [x1]",
input: "20 00 00 f9",
expectedMatch: /str\s+x0,\s*\[x1\]/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 bl (branch link)",
input: "00 00 00 94",
expectedMatch: /bl\s+/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 cbz x0",
input: "00 00 00 b4",
expectedMatch: /cbz\s+x0/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 cbnz x0",
input: "00 00 00 b5",
expectedMatch: /cbnz\s+x0/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 sub sp, sp, #0x20",
input: "ff 83 00 d1",
expectedMatch: /sub\s+sp,\s*sp/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 add sp, sp, #0x20",
input: "ff 83 00 91",
expectedMatch: /add\s+sp,\s*sp/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
// ==================== MULTI-INSTRUCTION TESTS ====================
{
name: "Disassemble ARM: ARM32 multiple instructions",
input: "00 48 2d e9 04 b0 8d e2 00 00 a0 e1 00 88 bd e8",
expectedMatch: /push.*\n.*add.*\n.*mov.*\n.*pop/s,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true],
},
],
},
{
name: "Disassemble ARM: ARM64 function prologue/epilogue",
input: "fd 7b bf a9 fd 03 00 91 00 00 80 52 fd 7b c1 a8 c0 03 5f d6",
expectedMatch: /stp.*\n.*mov.*\n.*mov.*\n.*ldp.*\n.*ret/s,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
// ==================== ADDRESS TESTS ====================
{
name: "Disassemble ARM: ARM64 with start address 0x1000",
input: "c0 03 5f d6",
expectedMatch: /0x00001000/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 4096, true, true],
},
],
},
{
name: "Disassemble ARM: ARM32 with start address 0x8000",
input: "00 00 a0 e1",
expectedMatch: /0x00008000/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Little Endian", 32768, true, true],
},
],
},
// ==================== ENDIANNESS TESTS ====================
{
name: "Disassemble ARM: ARM32 Big Endian",
input: "e1 a0 00 00",
expectedMatch: /mov\s+r0,\s*r0/,
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM (32-bit)", "ARM", "Big Endian", 0, true, true],
},
],
},
// ==================== EDGE CASES ====================
{
name: "Disassemble ARM: Empty input",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "Disassemble ARM",
args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true],
},
],
},
]);