AgentStack
SKILL verified Apache-2.0 Self-run

Deobfuscation

skill-as0ler-skills-deobfuscation · by as0ler

Binary deobfuscation, unpacking, and string recovery for protected native binaries (ELF, PE, Mach-O). Use this when encountering obfuscated, packed, or virtualized code — including VMProtect, Themida, Code Virtualizer, Arxan/Irdeto, custom protectors, or unknown obfuscation. Triggers: any mention of 'deobfuscation', 'obfuscated', 'packed binary', 'string encryption', 'code virtualization', 'anti-…

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-as0ler-skills-deobfuscation

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Deobfuscation? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Binary Deobfuscation & Unpacking

Framework for identifying, analyzing, and defeating software protection schemes on native binaries. This skill provides the generic methodology; protector-specific techniques are in sub-skills.

Sub-Skills

| Protector | Reference | |-----------|-----------| | VMProtect | [vmprotect/SKILL.md](./vmprotect/SKILL.md) — VMP 2.x/3.x detection, section dumping, string deobfuscation, devirtualization, anti-debug bypass |


1. General Workflow

1. Detect    → identify the protector and features used
2. Bypass    → neutralize anti-debug / anti-tamper
3. Unpack    → dump decrypted code sections at runtime
4. Recover   → extract strings, reconstruct imports
5. Analyze   → decompile / devirtualize recovered code

2. Protector Identification

2.1 Quick Triage (radare2)

iS                    # unusual section names (.vmp0, .themida, .enigma, etc.)
iS~rwx                # RWX sections — strong packing indicator
ii | wc -l            # import count — very few = likely packed
iz | wc -l            # string count — very few = string encryption
ie                    # entry point — does it land in a non-.text section?
s entry0; pd 20       # entry point instructions — push/call/jmp patterns

2.2 Protector Signatures

| Protector | Section Names | Entry Pattern | Key Imports | Other Indicators | |-----------|--------------|---------------|-------------|------------------| | VMProtect | .vmp0, .vmp1, .VMP | Jump into VMP section | dlsym/GetProcAddress only | VM dispatch loops, handler tables | | Themida/WL | .themida, .winlice | pushad; call $+5 | Kernel-level anti-debug | Driver-based protection | | Code Virtualizer | .cv0, .cv1 | Similar to VMP | Few imports | Oreans family (same vendor as Themida) | | Arxan/Irdeto | Normal names | Guard pages, integrity checks | Normal imports | Checksum functions, code guards | | UPX | UPX0, UPX1 | Decompression stub | Normal (after unpack) | upx -d works for unmodified UPX | | Custom | Varies | Varies | Often dlsym/mmap/mprotect | Manual analysis required |

2.3 Android .so Specific Detection

On Android, native libraries protected with VMP or similar tools show:

readelf -S target.so | grep -iE 'vmp|themida|cv[0-9]'   # named sections
readelf -l target.so | grep RWE                           # RWX segments
readelf -d target.so | grep NEEDED                        # minimal dependencies
  • RWX segments (PF_R | PF_W | PF_X) — legitimate .so files almost never need this
  • JNIOnLoad doing heavy work — protectors use JNIOnLoad as the unpacking entry point
  • mprotect storms at startup — the library calls mprotect repeatedly to make sections writable, unpack, then re-protect

3. Generic Dynamic Techniques

These techniques work regardless of the protector. They hook at the API boundary rather than trying to reverse the obfuscation internals.

3.1 Module Fingerprinting (Android)

Identify the target .so by matching a known export at an expected offset:

function findTargetModule(exportName, expectedOffset) {
    for (const mod of Process.enumerateModules()) {
        const exp = mod.findExportByName(exportName);
        if (exp && exp.equals(mod.base.add(expectedOffset))) {
            return mod;
        }
    }
    return null;
}

This avoids name-based matching (which breaks when the library is renamed) and works even when the library hasn't been loaded yet when combined with a dlopen hook.

3.2 Library Load Detection

Wait for a target library to load at runtime:

function waitForLibrary(identifyFn, callback) {
    var found = identifyFn();
    if (found) { callback(found); return; }

    var dlopen = Module.getGlobalExportByName('android_dlopen_ext')
        ?? Module.getGlobalExportByName('dlopen');

    Interceptor.attach(dlopen, {
        onLeave() {
            if (found) return;
            found = identifyFn();
            if (found) callback(found);
        }
    });
}

3.3 mprotect Monitoring

Track memory permission changes to understand the unpacking sequence:

function hookMprotect(targetModule) {
    var log = [];
    Interceptor.attach(Module.getGlobalExportByName('mprotect'), {
        onEnter(args) {
            this.addr = args[0];
            this.len  = args[1].toUInt32();
            this.prot = args[2].toInt32();
        },
        onLeave(retval) {
            if (retval.toInt32() !== 0) return;

            var start = this.addr;
            var end   = start.add(this.len);
            var modStart = targetModule.base;
            var modEnd   = modStart.add(targetModule.size);
            if (start.compare(modEnd) >= 0 || end.compare(modStart) = 0 &&
           returnAddr.compare(targetEnd)  4096) return;
    if (isDuplicate(source, str)) return;
    send({ type: 'string', source: source, value: str, extra: extra || '' });
}

3.7 Python Host Script Pattern

Capture agent output and write categorized results:

import frida, sys, signal, time

strings_by_source = {}

def on_message(message, data):
    if message["type"] == "send":
        payload = message["payload"]
        if isinstance(payload, dict) and payload.get("type") == "string":
            source = payload.get("source", "unknown")
            value = payload.get("value", "")
            strings_by_source.setdefault(source, []).append(value)
            print(f"[{source}] {value}")
        elif isinstance(payload, dict) and payload.get("type") == "dump":
            with open(f"dump_{payload['reason']}.bin", "wb") as f:
                f.write(data)
            print(f"[dump] {payload['size']} bytes saved")

device = frida.get_device_manager().add_remote_device("127.0.0.1:27042")
pid = device.spawn(["com.example.app"])
session = device.attach(pid)
script = session.create_script(open("agent.js").read())
script.on("message", on_message)
script.load()
device.resume(pid)

signal.signal(signal.SIGINT, lambda *_: sys.exit(0))
while True: time.sleep(1)

3.8 uncompress / zlib Monitoring

Many protectors (including VMProtect) use zlib to decompress packed code at runtime:

var uncompress = Module.getGlobalExportByName('uncompress');
if (uncompress) {
    Interceptor.attach(uncompress, {
        onEnter(args) {
            this.dest    = args[0];
            this.destLen = args[1];
            this.srcLen  = args[3].toUInt32();
        },
        onLeave(retval) {
            if (retval.toInt32() !== 0) return;
            var outLen = this.destLen.readU32();
            console.log('[uncompress] ' + this.srcLen + ' -> ' + outLen + ' bytes @ ' + this.dest);
        }
    });
}

4. Tips

  • Hook consumers, not decryptors. Reversing the decryption algorithm is hard and protector-specific. Hooking the APIs that receive decrypted data (strcmp, JNI, dlsym) is protector-agnostic and much faster.
  • Filter by return address. Without filtering, libc hooks produce thousands of irrelevant hits from system libraries. Check this.returnAddress against the target module range.
  • Dump after JNI_OnLoad (Android). For protected Android .so files, the unpacking is typically complete by the time JNI_OnLoad is called. Hook it and dump sections from onEnter.
  • Track mprotect to understand unpacking. The sequence of mprotect calls reveals which sections are being unpacked and when. This tells you the right moment to dump.
  • Use fingerprinting, not names. Identify target modules by matching exports at known offsets, not by library name (which can change between builds).

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.