Compare commits

..
12 Commits
18 changed files with 536 additions and 683 deletions

No files matched your search

+32
View File
@@ -47,3 +47,35 @@ jobs:
- name: Test if r2plugin is available
run: |
r2 -q -c La~hbc -c Lb~hbc -c Lc~hbc -- | grep -E "(core|arch|bin).*hbc"
meson-r2git:
name: Test unified r2plugin with Meson
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install build dependencies
run: sudo apt-get update -y && sudo apt-get install -y build-essential meson ninja-build pkg-config
- name: Build radare2 from git with Meson
run: |
git clone --depth=1 https://github.com/radareorg/radare2.git
cd radare2
# Clone r2hermes into the plugins directory and configure XPS
cp -f ../r2plugin/config.mk libr/xps/config.mk
git clone .. libr/xps/p/r2hermes
make -C libr/xps/p/r2hermes include/hbc/version.h
make -C libr/xps/p/r2hermes
make -C libr/xps
# Build radare2 with meson
meson setup b --prefix=/usr
meson compile -C b
sudo meson install -C b
- name: Test if r2plugin is available
run: |
r2 -q -c La~hbc -c Lb~hbc -c Lc~hbc -- | grep -E "(core|arch|bin).*hbc"
+5 -3
View File
@@ -4,13 +4,15 @@
![Meson](https://github.com/radareorg/r2hermes/actions/workflows/meson.yml/badge.svg)
![Windows](https://github.com/radareorg/r2hermes/actions/workflows/windows.yml/badge.svg)
Hermes bytecode disassembler, decompiler, and radare2 plugins.
Meta's React Native Hermes bytecode assembler, disassembler, decompiler, and compation radare2 plugins.
Supports Hermes bytecode **v51 - v96** (React Native / Meta).
<p align="center">
<img width="250px" height="500px" src="r2hermes500.png" />
</p>
## Features
- Disassemble Hermes bytecode
- Disassemble Hermes bytecode (v51 .. v96)
- Decompile to JavaScript pseudocode
- Assemble instructions
- Parse binary headers
-42
View File
@@ -1,42 +0,0 @@
#ifndef HERMES_DEC_OPTIMIZER_H
#define HERMES_DEC_OPTIMIZER_H
#include <hbc/common.h>
#include <hbc/decompilation/token.h>
typedef enum {
OPTIMIZER_NONE = 0,
OPTIMIZER_CONSTANT_PROPAGATION = 1,
OPTIMIZER_DEAD_CODE_ELIMINATION = 2,
OPTIMIZER_EXPRESSION_INLINING = 4,
OPTIMIZER_ALL = 7
} OptimizerFlags;
/* Constant value type */
typedef enum {
CONST_TYPE_NONE,
CONST_TYPE_NUMBER,
CONST_TYPE_BOOL,
CONST_TYPE_NULL,
CONST_TYPE_UNDEFINED,
CONST_TYPE_STRING
} ConstType;
typedef struct {
ConstType type;
union {
double num_val;
bool bool_val;
const char *str_val;
} val;
} ConstValue;
/* Token optimization pass - processes and transforms token sequences */
Result optimizer_run_constant_propagation(TokenString *ts);
Result optimizer_run_dead_code_elimination(TokenString *ts);
Result optimizer_run_expression_inlining(TokenString *ts);
/* Main optimizer entry point */
Result optimizer_run(TokenString *ts, OptimizerFlags flags);
#endif /* HERMES_DEC_OPTIMIZER_H */
+1 -40
View File
@@ -239,23 +239,6 @@ Result hbc_decomp_all(
HBCDecompOptions options,
char **out_str);
/**
* Disassemble a specific function.
*/
Result hbc_disasm_fn(
HBC *hbc,
u32 function_id,
HBCDisOptions options,
char **out_str);
/**
* Disassemble all functions.
*/
Result hbc_disasm_all(
HBC *hbc,
HBCDisOptions options,
char **out_str);
/**
* Single instruction decode output
*/
@@ -296,14 +279,6 @@ typedef struct {
u32 count;
} HBCInsns;
/**
* Decode a function into an array of instructions.
*/
Result hbc_decode_fn(
HBC *hbc,
u32 function_id,
HBCInsns *out);
/**
* Free instruction array.
*/
@@ -339,6 +314,7 @@ typedef struct {
/* HBC context (optional, for object/array buffer resolution) */
HBC *hbc;
bool build_objects;
} HBCDecodeCtx;
/**
@@ -346,21 +322,6 @@ typedef struct {
*/
Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out);
/**
* Decode a single instruction from raw bytes.
* @param hbc Optional HBC context for object/array buffer resolution (can be NULL)
*/
Result hbc_dec_insn(
const u8 *bytes,
size_t len,
u32 bytecode_version,
u64 pc,
bool asm_syntax,
bool resolve_string_ids,
const HBCStrs *string_ctx,
HBC *hbc,
HBCInsnInfo *out);
/* ============================================================================
* Instruction Encoding
* ============================================================================ */
+1 -1
View File
@@ -1,7 +1,7 @@
project(
'r2hermes',
'c',
version: '1.0.0',
version: '1.1.0',
license: 'MIT',
default_options: [
'c_std=c11',
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="200mm"
height="200mm"
viewBox="0 0 200 200"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
sodipodi:docname="r2hermes.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.65979082"
inkscape:cx="365.26728"
inkscape:cy="427.40819"
inkscape:window-width="1544"
inkscape:window-height="987"
inkscape:window-x="222"
inkscape:window-y="44"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" /><defs
id="defs1" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="fill:#000000;stroke-width:2.07237"
d="m 65.405259,182.40488 c 8.08363,-1.31155 15.545839,-5.228 27.944365,-14.66619 3.105915,-2.36433 5.83115,-4.29879 6.0561,-4.29879 0.22491,0 3.287466,2.24953 6.805676,4.99895 12.97156,10.13707 22.71012,14.36051 32.02796,13.88998 8.20183,-0.41416 12.80798,-4.26906 16.08053,-13.45777 1.91346,-5.37259 2.29499,-20.84084 0.73918,-29.96811 -0.66776,-3.91748 -0.95174,-7.34867 -0.63106,-7.62487 0.32066,-0.27621 3.54393,-1.74852 7.1628,-3.27179 11.88898,-5.00441 18.05863,-8.46822 22.31527,-12.52834 11.35847,-10.83415 11.16153,-20.986376 -0.61644,-31.778748 -5.03438,-4.613098 -14.86053,-10.036823 -24.31596,-13.421646 l -5.54144,-1.98371 0.96825,-4.882283 c 1.5104,-7.615982 2.03694,-21.007379 1.05512,-26.834672 -2.53415,-15.040824 -11.30206,-22.001727 -24.07884,-19.116366 -8.57482,1.93644 -16.48902,6.477513 -30.57361,17.54278 -1.688876,1.326834 -1.718076,1.312777 -9.566219,-4.604651 -25.39237,-19.145631 -42.985514,-17.789987 -48.051634,3.702633 -1.43768,6.099251 -1.423548,17.269444 0.03345,26.433827 0.638137,4.013848 0.884878,7.536494 0.548313,7.828101 -0.336567,0.291609 -2.750361,1.28414 -5.363986,2.205627 -2.613629,0.921487 -7.054966,2.828803 -9.86964,4.238481 -26.2070036,13.125292 -29.8334427,29.994037 -9.54137,44.382647 5.359465,3.80026 11.124311,6.71237 19.47139,9.83592 l 6.274598,2.34801 -1.088676,5.24951 c -0.655525,3.16087 -1.115861,9.61263 -1.157005,16.21576 -0.05999,9.62896 0.135018,11.55446 1.599087,15.78953 3.105332,8.98262 7.427923,12.83351 16.244148,14.4715 0.20104,0.0374 2.482391,-0.27553 5.069643,-0.69532 z m -8.852167,-10.12344 c -3.751344,-2.97305 -6.085299,-14.77458 -4.985876,-25.21082 0.888267,-8.43185 1.555743,-12.11741 2.275049,-12.56197 0.34399,-0.21259 5.646833,0.46084 11.784093,1.49653 12.711453,2.14513 11.070753,1.1235 20.576734,12.81302 l 6.242461,7.6764 -2.71268,2.39074 c -9.130327,8.04677 -19.716332,13.76414 -26.838471,14.49514 -3.77633,0.38757 -4.658421,0.23469 -6.34131,-1.09904 z m 73.818708,0.0207 c -5.40534,-1.77815 -9.64358,-4.27951 -17.92297,-10.57797 l -6.73906,-5.12666 3.13901,-3.70679 c 1.72644,-2.03872 5.1939,-6.25609 7.70544,-9.37193 2.51155,-3.11585 5.28303,-5.82911 6.15887,-6.02949 1.94032,-0.44394 20.59258,-3.29257 21.55902,-3.29257 1.08625,0 2.65075,11.33101 2.67497,19.37373 0.0391,13.01979 -2.82778,19.24338 -9.12516,19.80859 -1.9941,0.17904 -4.91659,-0.24347 -7.45012,-1.07691 z m -36.172965,-28.07332 -4.518716,-5.28005 4.701481,-0.21618 c 2.585816,-0.11889 6.856,-0.11889 9.4893,0 l 4.7878,0.21618 -4.49901,5.30037 c -2.47443,2.91518 -4.711196,5.29121 -4.970576,5.28005 -0.25936,-0.011 -2.504992,-2.39635 -4.990279,-5.30037 z m -7.77842,-14.96041 -4.255191,-0.26532 -4.355269,-6.72452 c -5.439438,-8.39848 -11.625669,-20.34073 -11.625669,-22.442863 0,-2.105876 6.189501,-14.049411 11.650254,-22.480845 l 4.379853,-6.76253 h 16.819551 16.819506 l 3.5512,5.30036 c 4.06848,6.072519 10.45604,17.295145 12.14627,21.340435 l 1.14799,2.747513 -4.11174,8.03841 c -2.26146,4.42112 -6.00156,10.91641 -8.3113,14.43398 l -4.19957,6.39559 -6.12059,0.47639 c -6.08243,0.47343 -15.393643,0.45101 -23.535295,-0.0566 z m -23.660648,-3.01883 c -3.290834,-0.65524 -6.166359,-1.38013 -6.390062,-1.61089 -0.436965,-0.45069 3.070702,-11.61604 3.958794,-12.60133 0.40219,-0.4462 7.771942,11.34737 9.389357,15.02548 0.28811,0.6552 0.599983,0.69163 -6.958089,-0.81326 z m 65.732503,0.89746 c 6.64942,-11.94184 8.90852,-15.59436 9.3454,-15.10968 0.83096,0.92192 4.38345,12.05732 4.02936,12.63022 -0.17345,0.28066 -2.7153,0.91951 -5.64856,1.41968 -2.93327,0.50016 -5.95013,1.08422 -6.70417,1.2979 -0.75403,0.21368 -1.21393,0.10652 -1.02203,-0.23812 z M 40.005184,119.92199 C 29.137487,115.74039 18.735382,108.57757 16.083732,103.44985 13.899781,99.226547 14.580281,97.184361 19.943193,91.867578 25.253006,86.603437 33.09917,82.027199 41.911899,79.054431 l 4.850434,-1.636179 4.235148,11.284153 4.23515,11.284154 -4.136306,10.890761 c -2.99014,7.87294 -4.460892,10.88087 -5.30767,10.85503 -0.644249,-0.0186 -3.24681,-0.83432 -5.783471,-1.81036 z m 107.143396,-9.20743 -4.20703,-11.051106 4.13229,-10.880221 c 3.12305,-8.22283 4.44062,-10.880218 5.39457,-10.880218 2.21207,0 13.66146,4.960442 18.49426,8.012637 2.56597,1.620561 6.35959,4.737825 8.43028,6.927254 3.29211,3.480926 3.76485,4.362967 3.76485,7.024508 0,2.787706 -0.4151,3.454746 -4.93483,7.930046 -3.06936,3.03918 -6.92045,5.91803 -10.18754,7.61558 -5.43168,2.82227 -14.20922,6.35595 -15.78264,6.35383 -0.49344,-6.6e-4 -2.79033,-4.97421 -5.10421,-11.05231 z M 59.992728,87.32898 c -2.345369,-6.331151 -3.945993,-11.996043 -3.499922,-12.386837 0.600454,-0.526047 12.734475,-3.012835 13.068922,-2.678391 0.22244,0.222439 -7.925708,14.81773 -8.7104,15.602422 -0.264717,0.264716 -0.651086,0.02298 -0.8586,-0.537194 z m 72.901162,-7.023738 c -2.4819,-4.300108 -4.41643,-7.914508 -4.29895,-8.032001 0.33879,-0.338789 12.46534,2.131868 13.07301,2.663491 0.525,0.459308 -2.783,11.529369 -3.77888,12.645862 -0.26542,0.297565 -2.51325,-2.977245 -4.99518,-7.277352 z M 52.413642,60.17422 c -2Line truncated
id="path1"
sodipodi:nodetypes="ssscsssssssscsssssssssssscssssssssssscsscsscsssssccsccccscsccssscccsscsscssssssscsssscsssscccscsscssssssssssssssssssssssscssssssscsssscsscssccc" /><path
style="fill:#1a1a1a;stroke-width:0.116743"
d="m 99.09343,86.984035 c -0.433378,0.01898 -1.279722,-0.04797 -1.166293,0.626811 -0.07594,1.725589 -0.01063,3.460018 -0.08505,5.180484 -1.583015,-2.272255 -3.661797,-4.374412 -6.38905,-5.173624 -1.820358,-0.634195 -3.799636,-0.502714 -5.687046,-0.337252 -2.578839,0.461946 -4.988,2.259769 -5.748925,4.831396 -1.044344,3.15068 0.554172,6.85477 3.396683,8.47144 2.819126,1.35582 6.537812,1.15347 8.845863,-1.10658 1.26105,-1.14881 1.988894,-2.83896 1.858987,-4.55045 0.04593,-1.18872 -0.0836,-2.39009 0.02806,-3.57187 0.739949,-0.12582 1.19556,0.73185 1.596618,1.21831 1.406494,1.95756 1.5765,4.4103 1.604586,6.73006 0.112076,5.45862 0.04861,10.93116 0.06875,16.39512 -1.50075,0.0423 -3.039804,-0.083 -4.517192,0.0616 0.07047,0.51904 -0.422063,1.8136 0.477918,1.66633 7.588321,0 15.176631,0 22.764951,0 0.22573,-0.34655 0.38535,-1.573 -0.11674,-1.7279 -0.9962,0 -1.99239,0 -2.98859,0 0,-8.80241 0,-17.60483 0,-26.407248 1.0731,-0.04054 2.18324,0.081 3.23302,-0.06156 -0.0571,-0.49214 0.39445,-1.669191 -0.46772,-1.548211 -5.0858,-0.237955 -10.17665,-0.369779 -15.25934,-0.677209 -0.48295,-0.01632 -0.96624,-0.02889 -1.44949,-0.01961 z"
id="path68"
inkscape0:connector-curvature="0" /></g></svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

+5 -3
View File
@@ -22,10 +22,12 @@ configure_file(
install: true,
)
# Include directories
# Register plugins in the static plugin lists
core_plugins += ['r2hermes']
# Include directories (relative to this meson.build file location)
include_dirs = include_directories(join_paths('..', 'include'))
r_core_additional_inc += include_dirs
r_core_additional_inc += ['../xps/p/r2hermes/include']
# Library source files
lib_utils_src = files(
@@ -56,7 +58,7 @@ lib_opcodes_src = files(
lib_core_src = files(
'../src/lib/hbc.c',
'../src/lib/r2.c',
'../src/r2/core_hbc.c',
'../src/r2/core_hbc_one.c',
)
lib_src = lib_utils_src + lib_parsers_src + lib_disasm_src + lib_decompile_src + lib_opcodes_src + lib_core_src
+3
View File
@@ -1,3 +1,6 @@
#ifdef R2_CORE_H
#include "core/deps.h"
#endif
#ifdef R2_ARCH_H
#include "arch/deps.h"
#endif
-162
View File
@@ -1,162 +0,0 @@
#include <hbc/decompilation/optimizer.h>
#include <hbc/decompilation/token.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
/* Constant Propagation Pass - analyzes constant assignments */
Result optimizer_run_constant_propagation(TokenString *ts) {
if (!ts) {
return ERROR_RESULT(RESULT_ERROR_INVALID_ARGUMENT, "null tokenstring");
}
/* Track register values during forward scan */
typedef struct {
int reg_num;
double value;
bool is_constant;
} RegValue;
RegValue *regs = (RegValue *)malloc(sizeof(RegValue) * 256);
if (!regs) {
return ERROR_RESULT(RESULT_ERROR_MEMORY_ALLOCATION, "oom");
}
memset(regs, 0, sizeof(RegValue) * 256);
/* Scan tokens and detect patterns:
1. r_N = constant (mark reg as constant)
2. r_N = r_A op r_B where r_A, r_B are constants (fold and compute)
*/
Token *t = ts->head;
while (t) {
t = t->next;
}
free(regs);
return SUCCESS_RESULT();
}
/* Dead Code Elimination - marks unused register definitions for potential removal */
Result optimizer_run_dead_code_elimination(TokenString *ts) {
if (!ts) {
return ERROR_RESULT(RESULT_ERROR_INVALID_ARGUMENT, "null tokenstring");
}
/* Track which registers are actually used after being assigned */
typedef struct {
int reg_num;
bool is_used;
int use_count;
} RegUse;
RegUse *reg_uses = (RegUse *)malloc(sizeof(RegUse) * 256);
if (!reg_uses) {
return ERROR_RESULT(RESULT_ERROR_MEMORY_ALLOCATION, "oom");
}
memset(reg_uses, 0, sizeof(RegUse) * 256);
/* Forward pass: track register usage */
Token *t = ts->head;
while (t) {
if (t->type == TOKEN_TYPE_RIGHT_HAND_REG) {
RightHandRegToken *rhs = (RightHandRegToken *)t;
if (rhs->reg_num < 256) {
reg_uses[rhs->reg_num].is_used = true;
reg_uses[rhs->reg_num].use_count++;
}
}
t = t->next;
}
/* Identify unused registers:
- Defined (LHS) but never used (RHS)
- These can potentially be eliminated (future optimization)
*/
free(reg_uses);
return SUCCESS_RESULT();
}
/* Expression Inlining - inline simple single-use expressions */
Result optimizer_run_expression_inlining(TokenString *ts) {
if (!ts) {
return ERROR_RESULT(RESULT_ERROR_INVALID_ARGUMENT, "null tokenstring");
}
/* Identify inlining opportunities:
- Single register use
- Simple assignment (literal or single operation)
- Not critical for control flow
*/
typedef struct {
int reg_num;
int def_count;
int use_count;
Token *def_token;
} InlineCandidate;
InlineCandidate *candidates = (InlineCandidate *)malloc(sizeof(InlineCandidate) * 256);
if (!candidates) {
return ERROR_RESULT(RESULT_ERROR_MEMORY_ALLOCATION, "oom");
}
memset(candidates, 0, sizeof(InlineCandidate) * 256);
/* Count definitions and uses */
Token *t = ts->head;
while (t) {
if (t->type == TOKEN_TYPE_LEFT_HAND_REG) {
LeftHandRegToken *lhs = (LeftHandRegToken *)t;
if (lhs->reg_num < 256) {
candidates[lhs->reg_num].def_count++;
if (candidates[lhs->reg_num].def_count == 1) {
candidates[lhs->reg_num].def_token = t;
}
}
} else if (t->type == TOKEN_TYPE_RIGHT_HAND_REG) {
RightHandRegToken *rhs = (RightHandRegToken *)t;
if (rhs->reg_num < 256) {
candidates[rhs->reg_num].use_count++;
}
}
t = t->next;
}
/* Candidates for inlining: defined once, used once or twice (threshold) */
for (int i = 0; i < 256; i++) {
if (candidates[i].def_count == 1 && candidates[i].use_count <= 2) {
/* This register is a candidate for inlining */
/* Future: perform actual inlining here */
}
}
free(candidates);
return SUCCESS_RESULT();
}
/* Main optimizer dispatcher */
Result optimizer_run(TokenString *ts, OptimizerFlags flags) {
if (!ts) {
return ERROR_RESULT(RESULT_ERROR_INVALID_ARGUMENT, "null tokenstring");
}
Result r;
if (flags & OPTIMIZER_CONSTANT_PROPAGATION) {
r = optimizer_run_constant_propagation(ts);
if (r.code != RESULT_SUCCESS) return r;
}
if (flags & OPTIMIZER_DEAD_CODE_ELIMINATION) {
r = optimizer_run_dead_code_elimination(ts);
if (r.code != RESULT_SUCCESS) return r;
}
if (flags & OPTIMIZER_EXPRESSION_INLINING) {
r = optimizer_run_expression_inlining(ts);
if (r.code != RESULT_SUCCESS) return r;
}
return SUCCESS_RESULT();
}
+1 -1
View File
@@ -1133,7 +1133,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
}
case OP_DeclareGlobalVar:
{
RETURN_IF_ERROR (add (out, create_raw_token ("declare extern var ")));
RETURN_IF_ERROR (add (out, create_raw_token ("declare extern var")));
u32 sid = insn->arg1;
RETURN_IF_ERROR (add (out, unquoted_string (insn->hbc_reader, sid)));
break;
+59 -124
View File
@@ -337,34 +337,6 @@ Result hbc_decomp_all(
return SUCCESS_RESULT ();
}
Result hbc_disasm_fn(
HBC *hbc,
u32 function_id,
HBCDisOptions options,
char **out_str) {
(void)function_id;
(void)options;
if (!hbc || !out_str) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
}
return ERROR_RESULT (RESULT_ERROR_NOT_IMPLEMENTED, "Disassembly via state not yet implemented");
}
Result hbc_disasm_all(
HBC *hbc,
HBCDisOptions options,
char **out_str) {
(void)options;
if (!hbc || !out_str) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
}
return ERROR_RESULT (RESULT_ERROR_NOT_IMPLEMENTED, "Disassembly via state not yet implemented");
}
Result hbc_all_funcs(
HBC *hbc,
HBCFuncArray *out) {
@@ -406,34 +378,40 @@ void hbc_free_funcs(HBCFuncArray *arr) {
}
}
Result hbc_decode_fn(
HBC *hbc,
u32 function_id,
HBCInsns *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
}
const u8 *bytecode = NULL;
u32 bytecode_size = 0;
Result res = hbc_get_function_bytecode (hbc, function_id, &bytecode, &bytecode_size);
if (res.code != RESULT_SUCCESS) {
return res;
}
HBCStrs string_tables = { 0 };
res = hbc_get_string_tables (hbc, &string_tables);
if (res.code != RESULT_SUCCESS) {
return res;
}
out->instructions = NULL;
out->count = 0;
return ERROR_RESULT (RESULT_ERROR_NOT_IMPLEMENTED, "Instruction decoding via state not yet implemented");
}
/* Single-instruction decode functions */
static char *build_literal_comment(HBCReader *reader, u8 opcode, const u32 *ovs, const char *base_text) {
const bool is_obj = (opcode == OP_NewObjectWithBuffer || opcode == OP_NewObjectWithBufferLong);
StringBuffer sb;
if (_hbc_string_buffer_init (&sb, 256).code != RESULT_SUCCESS) {
return NULL;
}
Result fmt_res;
if (is_obj) {
const u32 key_count = ovs[1];
const u32 value_count = ovs[2];
const u32 keys_id = ovs[3];
const u32 values_id = ovs[4];
fmt_res = _hbc_format_object_literal (reader, key_count, value_count, keys_id, values_id, &sb, LITERALS_PRETTY_NEVER, true);
} else {
const u32 value_count = ovs[2];
const u32 array_id = ovs[3];
fmt_res = _hbc_format_array_literal (reader, value_count, array_id, &sb, LITERALS_PRETTY_NEVER, true);
}
char *result = NULL;
if (fmt_res.code == RESULT_SUCCESS && sb.length > 0) {
size_t base_len = strlen (base_text);
result = (char *)malloc (base_len + 4 + sb.length + 1);
if (result) {
memcpy (result, base_text, base_len);
memcpy (result + base_len, " ; ", 4);
memcpy (result + base_len + 4, sb.data, sb.length + 1);
}
}
_hbc_string_buffer_free (&sb);
return result;
}
Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
if (!ctx || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid context");
@@ -445,30 +423,7 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
hbc_debug_printf ("Warning: bytecode version not specified, defaulting to %u\n", bytecode_version);
}
return hbc_dec_insn (
ctx->bytes,
ctx->len,
bytecode_version,
ctx->pc,
ctx->asm_syntax,
ctx->resolve_string_ids,
ctx->string_tables,
ctx->hbc,
out);
}
Result hbc_dec_insn(
const u8 *bytes,
size_t len,
u32 bytecode_version,
u64 pc,
bool asm_syntax,
bool resolve_string_ids,
const HBCStrs *string_ctx,
HBC *hbc,
HBCInsnInfo *out) {
if (!bytes || !out || len == 0) {
if (!ctx->bytes || !out || ctx->len == 0) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
}
@@ -479,7 +434,7 @@ Result hbc_dec_insn(
}
/* Read opcode */
u8 opcode = bytes[0];
u8 opcode = ctx->bytes[0];
/* Find instruction definition */
const Instruction *inst = NULL;
@@ -522,7 +477,7 @@ Result hbc_dec_insn(
default: need = 0; break;
}
if (pos + need > len) {
if (pos + need > ctx->len) {
break;
}
@@ -531,19 +486,19 @@ Result hbc_dec_insn(
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_UINT8:
case OPERAND_TYPE_ADDR8:
operand_values[i] = bytes[pos];
operand_values[i] = ctx->bytes[pos];
pos += 1;
break;
case OPERAND_TYPE_UINT16:
operand_values[i] = bytes[pos] | (bytes[pos + 1] << 8);
operand_values[i] = ctx->bytes[pos] | (ctx->bytes[pos + 1] << 8);
pos += 2;
break;
case OPERAND_TYPE_REG32:
case OPERAND_TYPE_UINT32:
case OPERAND_TYPE_IMM32:
case OPERAND_TYPE_ADDR32:
operand_values[i] = bytes[pos] | (bytes[pos + 1] << 8) |
(bytes[pos + 2] << 16) | (bytes[pos + 3] << 24);
operand_values[i] = ctx->bytes[pos] | (ctx->bytes[pos + 1] << 8) |
(ctx->bytes[pos + 2] << 16) | (ctx->bytes[pos + 3] << 24);
pos += 4;
break;
case OPERAND_TYPE_DOUBLE:
@@ -563,7 +518,7 @@ Result hbc_dec_insn(
/* Convert instruction name to snake_case */
hbc_camel_to_snake (inst->name, mnemonic, sizeof (mnemonic));
if (asm_syntax) {
if (ctx->asm_syntax) {
/* ASM syntax: mnemonic operand1, operand2, ... */
offset = snprintf (buf, sizeof (buf), "%s", mnemonic);
@@ -585,16 +540,16 @@ Result hbc_dec_insn(
} else if (operand_meaning == OPERAND_MEANING_STRING_ID) {
/* For string IDs, output the string storage offset (not the ID)
* so r2 can replace it with flags */
if (resolve_string_ids && string_ctx && string_ctx->string_storage_offset != 0) {
if (ctx->resolve_string_ids && ctx->string_tables && ctx->string_tables->string_storage_offset != 0) {
u32 str_offset = 0;
bool found = false;
/* Cast to proper types for access */
const StringTableEntry *small_table = (const StringTableEntry *)string_ctx->small_string_table;
const OffsetLengthPair *overflow_table = (const OffsetLengthPair *)string_ctx->overflow_string_table;
const StringTableEntry *small_table = (const StringTableEntry *)ctx->string_tables->small_string_table;
const OffsetLengthPair *overflow_table = (const OffsetLengthPair *)ctx->string_tables->overflow_string_table;
/* Look up string storage offset from string tables */
if (val < string_ctx->string_count && small_table) {
if (val < ctx->string_tables->string_count && small_table) {
if (small_table[val].length == 0xFF && overflow_table) {
/* Overflow string */
u32 overflow_idx = small_table[val].offset;
@@ -608,7 +563,7 @@ Result hbc_dec_insn(
if (found) {
/* Output the file-absolute string offset in hex - r2 will replace this with the string flag */
u32 file_offset = string_ctx->string_storage_offset + str_offset;
u32 file_offset = ctx->string_tables->string_storage_offset + str_offset;
offset += snprintf (buf + offset, sizeof (buf) - offset, "0x%x", file_offset);
} else {
/* Fallback: just show the ID */
@@ -631,41 +586,18 @@ Result hbc_dec_insn(
}
}
char *final_text = NULL;
if (hbc && (opcode == OP_NewObjectWithBuffer || opcode == OP_NewObjectWithBufferLong ||
opcode == OP_NewArrayWithBuffer || opcode == OP_NewArrayWithBufferLong)) {
HBCReader *reader = &hbc->reader;
StringBuffer sb;
if (_hbc_string_buffer_init (&sb, 256).code == RESULT_SUCCESS) {
Result fmt_res = { .code = RESULT_ERROR_PARSING_FAILED };
if (opcode == OP_NewObjectWithBuffer || opcode == OP_NewObjectWithBufferLong) {
u32 key_count = operand_values[1];
u32 value_count = operand_values[2];
u32 keys_id = operand_values[3];
u32 values_id = operand_values[4];
fmt_res = _hbc_format_object_literal (reader, key_count, value_count, keys_id, values_id,
&sb, LITERALS_PRETTY_NEVER, true);
} else {
u32 value_count = operand_values[2];
u32 array_id = operand_values[3];
fmt_res = _hbc_format_array_literal (reader, value_count, array_id,
&sb, LITERALS_PRETTY_NEVER, true);
}
if (fmt_res.code == RESULT_SUCCESS && sb.data && sb.data[0]) {
size_t buf_len = strlen (buf);
size_t sb_len = strlen (sb.data);
final_text = (char *)malloc (buf_len + 4 + sb_len + 1);
if (final_text) {
memcpy (final_text, buf, buf_len);
memcpy (final_text + buf_len, " ; ", 4);
memcpy (final_text + buf_len + 4, sb.data, sb_len + 1);
}
}
_hbc_string_buffer_free (&sb);
if (ctx->build_objects && ctx->hbc) {
const bool is_literal_op = (opcode == OP_NewObjectWithBuffer || opcode == OP_NewObjectWithBufferLong ||
opcode == OP_NewArrayWithBuffer || opcode == OP_NewArrayWithBufferLong);
if (is_literal_op) {
char *with_comment = build_literal_comment (&ctx->hbc->reader, opcode, operand_values, buf);
out->text = with_comment ? with_comment : strdup (buf);
} else {
out->text = strdup (buf);
}
} else {
out->text = strdup (buf);
}
out->text = final_text ? final_text : strdup (buf);
out->size = (u32)pos;
out->opcode = opcode;
@@ -674,9 +606,12 @@ Result hbc_dec_insn(
out->is_call = (strncmp (mnemonic, "call", 4) == 0) || (strcmp (mnemonic, "construct") == 0);
out->jump_target = 0;
if (!out->is_jump && !strncmp (mnemonic, "save_generator", strlen ("save_generator"))) {
out->is_jump = true;
}
if (out->is_jump && operand_values[0] != 0) {
/* Calculate jump target (relative offset) */
out->jump_target = pc + (i32)operand_values[0];
out->jump_target = ctx->pc + (i32)operand_values[0];
}
return SUCCESS_RESULT ();
+2 -2
View File
@@ -831,7 +831,7 @@ Result _hbc_reader_read_string_tables(HBCReader *reader) {
u32 overflow_index = reader->small_string_table[i].offset;
if (overflow_index >= reader->header.overflowStringCount) {
/* Invalid overflow index */
reader->strings[i] = strdup("");
reader->strings[i] = strdup ("");
if (!reader->strings[i]) {
free (string_storage);
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate empty string");
@@ -851,7 +851,7 @@ Result _hbc_reader_read_string_tables(HBCReader *reader) {
/* Check bounds */
if (offset + (is_utf16? length * 2: length) > string_storage_size) {
fprintf (stderr, "Warning: String %u offset/length out of bounds, treating as empty string\n", i);
reader->strings[i] = strdup("");
reader->strings[i] = strdup ("");
if (!reader->strings[i]) {
free (string_storage);
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate empty string");
+18 -25
View File
@@ -1,4 +1,4 @@
/* radare2 - LGPL - Copyright 2025 - pancake */
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
#include <r_anal.h>
#include <hbc/hbc.h>
@@ -38,13 +38,6 @@ static ut32 detect_version_from_bin(RArchSession *s) {
return 96;
}
#define READ_REG8(b, pos) ((u8) (b)[(pos)])
#define READ_UINT8(b, pos) ((u8) (b)[(pos)])
#define READ_UINT16(b, pos) ((u16) ((b)[(pos)] | ((b)[(pos) + 1] << 8)))
#define READ_UINT32(b, pos) ((u32) ((b)[(pos)] | ((b)[(pos) + 1] << 8) | ((b)[(pos) + 2] << 16) | ((b)[(pos) + 3] << 24)))
#define READ_INT8(b, pos) ((i8) (b)[(pos)])
#define READ_INT32(b, pos) ((i32)READ_UINT32 (b, pos))
#include "esil.inc.c"
static bool load_string_tables(HermesArchSession *hs, RArchSession *s) {
@@ -98,6 +91,9 @@ static bool opcode_is_conditional(u8 opcode) {
case OP_JmpUndefined:
case OP_JmpUndefinedLong:
return true;
case OP_SaveGenerator:
case OP_SaveGeneratorLong:
return true;
default:
/* Relational and equality conditional jumps occupy 152..191 */
if (opcode >= OP_JLess && opcode <= OP_JStrictNotEqualLong) {
@@ -172,7 +168,7 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
if (meta_result.code == RESULT_SUCCESS) {
/* Set op->ptr to the file offset of the string.
* meta.offset already includes string_storage_file_offset */
op->ptr = (st64) meta.offset;
op->ptr = (st64)meta.offset;
}
}
}
@@ -235,6 +231,9 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
.string_tables = &string_tables,
.hbc = hs->hbc
};
if (mask & R_ARCH_OP_MASK_ESIL && mask & R_ARCH_OP_MASK_DISASM) {
ctx.build_objects = true;
}
HBCInsnInfo sinfo;
if (hbc_dec (&ctx, &sinfo).code != RESULT_SUCCESS) {
@@ -249,20 +248,8 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
/* Parse operands and set ptr for string/function references */
parse_operands_and_set_ptr (op, op->bytes, op->size, sinfo.opcode, hs);
if (sinfo.text) {
char mnemonic_raw[64];
const char *end = sinfo.text;
while (*end && *end != ' ' && *end != '\t') {
end++;
}
size_t len = end - sinfo.text;
if (len > 0 && len < sizeof (mnemonic_raw)) {
memcpy (mnemonic_raw, sinfo.text, len);
mnemonic_raw[len] = '\0';
char mnemonic[64];
hbc_snake_to_camel (mnemonic_raw, mnemonic, sizeof (mnemonic));
set_esil (op, mnemonic, op->bytes, op->addr);
}
if (mask & R_ARCH_OP_MASK_ESIL && sinfo.text) {
set_esil (op, op->bytes, op->addr);
}
if (sinfo.opcode == OP_Ret) {
@@ -277,6 +264,14 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
return true;
}
/* SaveGenerator instructions are conditional branches, not calls */
if (sinfo.opcode == OP_SaveGenerator || sinfo.opcode == OP_SaveGeneratorLong) {
op->type = R_ANAL_OP_TYPE_CJMP;
op->jump = sinfo.jump_target;
op->fail = op->addr + op->size;
return true;
}
if (sinfo.is_jump) {
op->type = R_ANAL_OP_TYPE_JMP;
}
@@ -494,8 +489,6 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
case OP_StartGenerator:
case OP_ResumeGenerator:
case OP_CompleteGenerator:
case OP_SaveGenerator:
case OP_SaveGeneratorLong:
case OP_DirectEval:
op->type = R_ANAL_OP_TYPE_CALL;
break;
+1 -1
View File
@@ -300,7 +300,7 @@ static RList *strings(RBinFile *bf) {
ptr->paddr = meta.offset;
ptr->vaddr = meta.offset;
ptr->size = meta.isUTF16 ? (meta.length * 2) : meta.length;
ptr->size = meta.isUTF16? (meta.length * 2): meta.length;
ptr->length = meta.length;
ptr->ordinal = i;
r_list_append (ret, ptr);
+275 -276
View File
File diff suppressed because it is too large. Load diff
+86 -3
View File
@@ -235,7 +235,20 @@ static Result cmd_dis(const CliContext *ctx, int argc, char **argv) {
HBCInsnInfo sinfo;
memset (&sinfo, 0, sizeof (sinfo));
Result r = hbc_dec_insn (bytes, bcount, 96, 0, asm_syntax, false, NULL, NULL, &sinfo);
HBCDecodeCtx dec_ctx = {
.bytes = bytes,
.len = bcount,
.bytecode_version = 96,
.pc = 0,
.asm_syntax = asm_syntax,
.resolve_string_ids = false,
.string_tables = NULL,
.hbc = NULL,
.build_objects = true
};
Result r = hbc_dec (&dec_ctx, &sinfo);
if (r.code != RESULT_SUCCESS) {
return r;
}
@@ -575,6 +588,77 @@ static Result cmd_cmp(const CliContext *ctx, int argc, char **argv) {
return SUCCESS_RESULT ();
}
static Result disasm_function(HBC *hbc, u32 function_id, char **out) {
const u8 *bytecode = NULL;
u32 bytecode_size = 0;
Result res = hbc_get_function_bytecode (hbc, function_id, &bytecode, &bytecode_size);
if (res.code != RESULT_SUCCESS) {
return res;
}
HBCHeader hdr;
res = hbc_get_header (hbc, &hdr);
if (res.code != RESULT_SUCCESS) {
return res;
}
HBCFunc fi;
res = hbc_get_function_info (hbc, function_id, &fi);
if (res.code != RESULT_SUCCESS) {
return res;
}
size_t cap = 4096;
size_t len = 0;
char *buf = malloc (cap);
if (!buf) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
buf[0] = '\0';
u32 offset = 0;
while (offset < bytecode_size) {
HBCInsnInfo insn_info;
memset (&insn_info, 0, sizeof (insn_info));
HBCDecodeCtx dec_ctx = {
.bytes = bytecode + offset,
.len = bytecode_size - offset,
.pc = fi.offset + offset,
.bytecode_version = hdr.version,
.asm_syntax = false,
.resolve_string_ids = false,
.string_tables = NULL,
.hbc = hbc,
.build_objects = false
};
res = hbc_dec (&dec_ctx, &insn_info);
if (res.code != RESULT_SUCCESS || insn_info.size == 0) {
free (insn_info.text);
break;
}
const char *text = insn_info.text? insn_info.text: "";
size_t text_len = strlen (text);
if (len + text_len + 2 > cap) {
cap *= 2;
char *tmp = realloc (buf, cap);
if (!tmp) {
free (insn_info.text);
free (buf);
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
buf = tmp;
}
memcpy (buf + len, text, text_len);
len += text_len;
buf[len++] = '\n';
buf[len] = '\0';
free (insn_info.text);
offset += insn_info.size;
}
*out = buf;
return SUCCESS_RESULT ();
}
static Result cmd_cf(const CliContext *ctx, int argc, char **argv) {
if (argc < 3) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: %s cf <input_file> <python_dis_file> <function_id>", ctx->program_name);
@@ -594,9 +678,8 @@ static Result cmd_cf(const CliContext *ctx, int argc, char **argv) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Invalid function id %u", function_id);
}
HBCDisOptions opt = (HBCDisOptions){ 0 };
char *disasm_str = NULL;
res = hbc_disasm_fn (hbc, function_id, opt, &disasm_str);
res = disasm_function (hbc, function_id, &disasm_str);
if (res.code != RESULT_SUCCESS) {
hbc_close (hbc);
return res;