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
55 changes: 24 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,45 @@
# libscompile

**libscompile** is an ultra-secure, minimalistic compiler and obfuscator for Frida scripts (supports Frida v12-v17). It uses a dynamic 256-bit RC4 stream cipher to fully encrypt your JavaScript payloads, making them practically impossible to reverse-engineer without the securely generated session key.
compiler and obfuscator for frida scripts (v12-17).
packs js into ast-obfuscated byte arrays with rc4 encryption and magic signature checks.

## 🚀 Features
- **Maximum Security:** The payload is encrypted with RC4. The key is *never* stored inside the compiled file.
- **Frida Compatible:** Works natively with Frida 12-17 out of the box.
- **Decompiled Aesthetics:** The generated decryption stub is deliberately designed to look like raw decompiled code, confusing automated analysis and AI detectors.
- **Dynamic Key Injection:** You provide the key at runtime (e.g., via your Python loader), keeping your logic 100% secure at rest.
### install

## ⚙️ Installation
Clone the repository and ensure you have Node.js installed:
```bash
git clone https://github.com/krkshs/libscompile.git
cd libscompile
npm install
```

## 🛠️ Usage
### build

### 1. Compile Your Script
Pass your raw Frida script to the compiler:
```bash
node libscompile.js your_script.js
node debug/cli.js script.js
```
The compiler will output:
- A new file: `your_script.obf.js`
- A randomly generated **Session Key** (e.g., `a2f35088c485fed1...`). **Save this key!**
outputs `script.obf.js` and a session key.

### 2. Injecting with Frida
Since the key is completely stripped from `your_script.obf.js`, you *must* pass it to the environment before the script executes.
### inject (python)

if the key is stripped from the build (e.g. main branch), pass it to the frida environment before eval:

Example via Python:
```python
import frida

# Your generated key from libscompile
OBF_KEY = "a2f35088c485fed1..."

with open("your_script.obf.js", "r") as f:
obfuscated_code = f.read()
with open("script.obf.js", "r") as f:
code = f.read()

# Prepend the key to the script
script_payload = f"var libskey = '{OBF_KEY}';\n" + obfuscated_code
payload = f"var libskey = 'your_key_here';\n" + code

# Inject
session = frida.attach("TargetApp")
script = session.create_script(script_payload)
script.load()
session = frida.attach("target")
session.create_script(payload).load()
```

## 📜 License
MIT License. Created by @krkshs.
### internals
- rc4 payload encryption
- magic signature verification (`LIBSMETA_OK`)
- silent abort on decryption failure
- ast obfuscation (control flow flattening, string splitting)
- byte array compilation format

---
@krkshs
9 changes: 9 additions & 0 deletions debug/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { bfridobf } = require('./compiler.js');

const ipath = process.argv[2];
if (!ipath) {
console.error('[-] missing input file');
process.exit(1);
}

bfridobf(ipath);
47 changes: 6 additions & 41 deletions libscompile.js → debug/compiler.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,6 @@
const fs = require('fs');
const crypto = require('crypto');

//core rc4 cipher stream
function gstream(khex, olen) {
const sbox = Array.from({ length: 256 }, (_, i) => i);
let mx = 0;
for (let i = 0; i < 256; i++) {
mx = (mx + sbox[i] + khex.charCodeAt(i % khex.length)) % 256;
[sbox[i], sbox[mx]] = [sbox[mx], sbox[i]];
}

let i = 0, j = 0;
const strm = [];
for (let n = 0; n < olen; n++) {
i = (i + 1) % 256;
j = (j + sbox[i]) % 256;
[sbox[i], sbox[j]] = [sbox[j], sbox[i]];
strm.push(sbox[(sbox[i] + sbox[j]) % 256]);
}
return strm;
}

//encrypt payload
function encdata(rbuf, kstr) {
const cbytes = gstream(kstr, rbuf.length);
let hdump = '';
for (let i = 0; i < rbuf.length; i++) {
const xrd = rbuf[i] ^ cbytes[i];
hdump += xrd.toString(16).padStart(2, '0');
}
return hdump;
}
const { encdata } = require('./crypto.js');

//process file routine
function bfridobf(tpath) {
Expand All @@ -47,11 +17,13 @@ function bfridobf(tpath) {
const okey = crypto.randomBytes(32).toString('hex');
const ehex = encdata(dbuf, okey);

const karr = Array.from(okey).map(c => c.charCodeAt(0)).join(',');

//frida injector stub
const scode = `// by obf @krkshs
// libscompile 0.1.2 (jake)
// libscompile 0.1.3 (pre-alpha)
!function(){
var k=typeof libskey!=='undefined'?libskey:'',h="${ehex}",s=[];
var k=String.fromCharCode(${karr}),h="${ehex}",s=[];
for(var i=0;i<256;i++)s[i]=i;
var j=0,t;
for(var i=0;i<256;i++){
Expand Down Expand Up @@ -83,11 +55,4 @@ function bfridobf(tpath) {
}
}

//run cli
const ipath = process.argv[2];
if (!ipath) {
console.error('[-] missing input file');
process.exit(1);
}

bfridobf(ipath);
module.exports = { bfridobf };
30 changes: 30 additions & 0 deletions debug/crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
function gstream(khex, olen) {
const sbox = Array.from({ length: 256 }, (_, i) => i);
let mx = 0;
for (let i = 0; i < 256; i++) {
mx = (mx + sbox[i] + khex.charCodeAt(i % khex.length)) % 256;
[sbox[i], sbox[mx]] = [sbox[mx], sbox[i]];
}

let i = 0, j = 0;
const strm = [];
for (let n = 0; n < olen; n++) {
i = (i + 1) % 256;
j = (j + sbox[i]) % 256;
[sbox[i], sbox[j]] = [sbox[j], sbox[i]];
strm.push(sbox[(sbox[i] + sbox[j]) % 256]);
}
return strm;
}

function encdata(rbuf, kstr) {
const cbytes = gstream(kstr, rbuf.length);
let hdump = '';
for (let i = 0; i < rbuf.length; i++) {
const xrd = rbuf[i] ^ cbytes[i];
hdump += xrd.toString(16).padStart(2, '0');
}
return hdump;
}

module.exports = { gstream, encdata };