Analyzing Shellcode logo

Analyzing Shellcode

Community
trilwu
analyzing-shellcode

Analyze raw shellcode and position-independent code — extracting the bytes, guessing architecture, disassembling at the right base, decoding self-decoder stubs, resolving hashed Windows APIs, emulating to the decoded stage, and pulling C2 and stage IOCs. Use when handed a raw blob of position-independent code, an extracted payload with no PE/ELF header, shellcode lifted from an exploit or a macro/loader, a `\x`-escaped or base64 buffer, a suspected Cobalt Strike or Metasploit stager, or an egg-hunter.

Overview

Publishertrilwu
Repositorysecskills
Skill nameanalyzing-shellcode
Stars
144
Forks
15
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by trilwu on GitHub. Read the source before you install it.

Installation

Install the Analyzing Shellcode AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/trilwu/secskills.git /tmp/secskills
mkdir -p .claude/skills
cp -r /tmp/secskills/secskills-defense/skills/analyzing-shellcode .claude/skills/analyzing-shellcode
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Analyzing Shellcode in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Analyzing Shellcode on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Analyzing Shellcode is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Analyzing Shellcode

Shellcode is code with no container — no PE/ELF header, no imports table, no fixed load address — so it must find everything it needs at runtime. That self-reliance is exactly what gives it away: the PEB walk, the API hashing, the position-independent GetPC trick are structural fingerprints you can read even before you know what the payload does.

When to Use

  • A raw blob of position-independent code with no PE/ELF/Mach-O header
  • A payload carved from an exploit, a macro, a loader, or a JS/HTA dropper
  • A \x-escaped, 0x-prefixed, base64, or XOR-obfuscated byte buffer
  • A suspected Metasploit stager (reverse_tcp/https) or Cobalt Strike beacon stage
  • An egg-hunter or GetPC/decoder stub found while triaging a larger binary
  • Bytes recovered from network capture, RWX memory, or a document stream

When NOT to Use

  • The sample is a normal PE/ELF/Mach-O with headers — use analyzing-binaries for the loader-backed disassembly workflow
  • You need broader sample triage or family ID — use analyzing-malware; come here only for the raw payload stage inside it
  • The blob is a packed executable, not raw shellcode — use unpacking-protected-binaries to unwrap it first
  • You are carving the shellcode out of a memory dump first — use analyzing-memory-images to locate and extract the region, then return here
  • Deep static reversing once decodedreverse-engineering is not a skill; route sustained disassembly work back to analyzing-binaries

Get the Bytes First

Shellcode arrives wrapped. Normalize it to a flat binary before anything else.

bash
# \x-escaped or 0x C-array pasted from an exploit → raw bytes
python3 -c "import sys,re; d=open('sc.txt').read(); \
  b=bytes(int(x,16) for x in re.findall(r'(?:0x|\\\\x)([0-9a-fA-F]{2})',d)); \
  open('sc.bin','wb').write(b); sys.stderr.write(f'{len(b)} bytes\n')"

# base64 buffer
base64 -d payload.b64 > sc.bin

# single-byte XOR brute (find the key that yields printable / low entropy)
python3 -c "import sys; d=open('sc.bin','rb').read(); \
  [print(k, bytes(c^k for c in d[:16]).hex()) for k in range(256)]"

Then sniff what you have before disassembling:

bash
file sc.bin && wc -c < sc.bin           # size: stagers ~200-800B, stages tens of KB
ent sc.bin 2>/dev/null || binwalk -E sc.bin   # high entropy → encoded/self-decoder

For document- and capture-borne shellcode, extract first: olevba --deobf for Office macros (analyzing-malware covers this), and carve from PCAP with tshark -r c.pcap --export-objects http,out/ or a tcp.stream follow, then treat the bytes as above.

First-Look Triage

bash
xxd sc.bin | head -40

Read the head for structure, not meaning:

  • Architecture guess. x86 shows 55 8B EC prologues, 64 A1 30 00 / 64 8B (fs:[0x30]) PEB access, and E8 00000000 5x call/pop. x64 shows 65 48 8B (gs:[0x60]), REX-prefixed 48 ..., and RSP alignment 48 83 EC. ARM/AArch64 is uniform 4-byte words with no x86 prologue signature.
  • GetPC trick. E8 00 00 00 00 58/59/5A/5B/5E (call $+5 then pop) or the fnstenv / D9 EE D9 74 24 F4 5B FPU trick recovers EIP for position-independence. Its presence means the code relocates itself and a decoder often follows.
  • Egg-hunter tags. Repeated 8-byte marker like w00tw00t (77 30 30 74 ×2) is an egg-hunter searching memory for the real payload.
  • Null-free / alnum encoding. No 00 bytes, or all-printable bytes, means an encoder stub (Metasploit alpha_mixed, shikata_ga_nai) wraps the real code — you are looking at the decoder, not the payload.

Static Disassembly at the Right Base

The single most common mistake is disassembling at the wrong architecture or mid-instruction. Try both widths and read which one produces a sane prologue.

bash
# objdump on a raw blob — set the arch explicitly, Intel syntax
objdump -D -b binary -m i386        -M intel sc.bin     # 32-bit
objdump -D -b binary -m i386:x86-64 -M intel sc.bin     # 64-bit

# ndisasm — quick, and -e skips a leading offset if the entry is not at 0
ndisasm -b 32 sc.bin
ndisasm -b 64 sc.bin

# rizin / radare2: assemble/disassemble a blob directly
rasm2 -a x86 -b 32 -d "$(xxd -p sc.bin | tr -d '\n')"
r2 -a x86 -b 32 -m 0x400000 sc.bin      # load raw at a base
#   e asm.arch=x86 ; e asm.bits=32 ; s 0 ; pd 60      disassemble entry
#   pdf @ 0                                            treat entry as a function

To load raw shellcode in Ghidra/IDA/Cutter: import as a raw binary, set the processor (x86:LE:32 or x86:LE:64), pick a base address, and define an entry point / disassemble at the true first instruction — not offset 0 if a length prefix or junk sled precedes it. Without a defined entry the auto-analyzer will disassemble data as code and the listing is noise.

Decode Self-Decoders

Encoded shellcode is a small decoder stub followed by an encrypted body. Do not try to read the body statically — disassemble only the stub, then let it run.

  • Recognize the stub. A short loop with xor/add/sub/rol against a running pointer, a length counter, and a key or key schedule. The loop's tail jumps back until the counter hits zero, then falls through into the decoded body.
  • shikata_ga_nai. Metasploit's polymorphic feedback XOR decoder: a GetPC block, then an XOR loop where each key word is mutated by the previous plaintext (additive feedback). It re-encodes differently every build, so a byte signature will not match — decode it by executing it, not by patterning.
  • Let it decode itself. Emulate or single-step the stub and dump memory once the loop exits; the region it wrote is the real stage. Then disassemble that.
bash
# scdbg dumps the decoded buffer and unpacks the second stage for x86 Windows
scdbg -f sc.bin -s -1                     # -s -1 lifts the step limit (default 2M)
scdbg -f sc.bin -d                       # dump self-modified/decoded memory

Windows API Resolution by Hash

Shellcode has no imports, so it walks the loaded-module list and resolves APIs by a hash of their names. Recognizing this is often the whole analysis.

  • PEB walk. fs:[0x30] (x86) or gs:[0x60] (x64) loads the PEB, then PEB->Ldr->InMemoryOrderModuleList is traversed to find kernel32.dll (or ntdll.dll) without a hardcoded address.
  • Export hashing. For each export name the stub computes a hash — commonly ror13 additive (the classic Metasploit block_api) or djb2 — and compares against a 4-byte constant. The constant is the API name, once you invert it.
  • Map hashes back to names. Precompute the hash over every export in the system DLLs and look up the constants you see:
bash
# Reproduce Metasploit block_api ror13 — the constant is hash(module)+hash(name)
python3 - <<'PY'
def ror13(b):
    h=0
    for c in b:
        h=((h>>13)|(h<<19))&0xffffffff; h=(h+c)&0xffffffff
    return h
mod=ror13("KERNEL32.DLL\0".encode('utf-16le'))   # module: UTF-16LE, uppercased
for api in ("LoadLibraryA","GetProcAddress","WinExec","VirtualAlloc"):
    print(hex((ror13((api+"\0").encode('ascii'))+mod)&0xffffffff), api)
PY

Tools: scdbg prints resolved API calls with arguments as it emulates; jmp2it/blob_runner under a debugger let you break at the resolver; export tables come from dllexp or rabin2 -E kernel32.dll. When the hash algorithm is custom, lift it into the Python harness and brute the export lists.

Emulation Is the Fast Path

For Windows x86 shellcode, emulation resolves the APIs, decodes the stages, and prints the behavior in seconds — reach for it before manual tracing.

bash
# scdbg — emulates x86 Windows shellcode, reports API calls, C2, and stages
scdbg -f sc.bin                          # default trace of resolved API calls
scdbg -f sc.bin /findsc                  # brute-force the correct start offset
scdbg -f sc.bin -foff 0x1a               # force entry at a known offset

# speakeasy — emulate x86/x64 shellcode or a raw module
speakeasy -t sc.bin --raw --arch x86     # --raw shellcode, --arch x86|amd64
speakeasy -t sc.bin --raw --arch amd64 -o report.json

For custom logic or non-Windows targets, build a harness:

  • Unicorn — map memory, set the registers, hook code/mem events, run, and read the decoded region back. Best when you need to script the decode.
  • Qiling — higher-level; emulates the OS layer so hashed-API resolution and VirtualAlloc/WinExec behave, with a rootfs and syscall interception.
  • Run under a debugger. blob_runner, shellcode2exe, or jmp2it wrap the blob into a loadable stub; open it in x32dbg/x64dbg, set EIP/RIP to the entry, and single-step through the decoder to the live stage.

Identify the Stage and Family

  • Metasploit stagers. reverse_tcp builds a socket then recvs a second stage into RWX memory and jumps to it; reverse_https uses WinInet (InternetOpen/HttpOpenRequest). scdbg/speakeasy surface the connect address and port directly.
  • Cobalt Strike. The beacon stage carries an encoded configuration; once decoded, parse it for C2 hosts, ports, URIs, user-agent, sleep/jitter, and the malleable profile. Community parsers (e.g. beacon config extractors) read the config block; hand a confirmed beacon to analyzing-malware.
  • Extract the endpoint. From the emulator log or the decoded config pull the C2 host/port and the request URI — these are the immediate blocking IOCs.
  • Compare against known decoders. shikata, call4_dword_xor, alpha_mixed, and the block_api resolver have recognizable shapes; matching one names the toolkit and tells you what the following bytes must be.

Safe Execution Discipline

Emulation is safe; actually running the shellcode is detonation. Apply the same containment analyzing-malware requires.

  • Isolated VM, snapshot taken before execution, reverted after.
  • No network, or a faked C2 (INetSim/FakeNet-NG) so the stager's callback resolves to your listener rather than the live operator.
  • Never let a stager reach the real C2 on an active incident — the callback tells the operator you are looking, and a second-stage download is a disclosure of your analysis.

Pull IOCs and Hand Off

Collect the durable indicators as you decode: C2 host/IP, port, request URI and user-agent, mutex names, the decoder key or hash constants, and the egg tag. Rank them as analyzing-malware does — behavior and config outlive rotating hosts.

Then route the work:

  • A byte/structure signature for the decoder or resolver → writing-yara-rules (author the rule), then engineering-detections (deploy it)
  • The dropped or downloaded second stage → analyzing-malware
  • Sustained disassembly of a large decoded stage → analyzing-binaries
  • Writing up the finding → reporting-security-findings

Rationalizations to Reject

  • "objdump showed garbage, so it's encrypted." More likely you disassembled at the wrong architecture, at the wrong offset, or straight through a decoder stub. Try both bit-widths and find the offset with a sane prologue first.
  • "There are no imports, so I can't tell what it calls." Shellcode resolves APIs by hash. Find the PEB walk and invert the hash constants.
  • "I'll just run it to see what it does." Not without an isolated, network-controlled harness. Emulate with scdbg/speakeasy instead.
  • "A byte signature will catch this stager." Not a polymorphic one — shikata_ga_nai re-encodes every build. Signature the decoded stage or the structural resolver, not the encoder.
  • "No obvious C2 in the strings." The address is built at runtime and the config is encoded. Decode the stub, then emulate to read the connect call.
  • "It starts at offset 0." Often it does not — a length prefix, alignment sled, or GetPC block precedes the entry. Use /findsc or read for the true first instruction.
  • "The disassembler auto-analyzed it, so the listing is right." On a headerless blob the analyzer guesses the entry and treats data as code. Define the real entry and processor, or the whole listing is misaligned.

ATT&CK Coverage

Generated from secskills-core/ttp-index.json — edit that file, then run python3 scripts/sync_attack.py --write. Re-verify IDs against the current ATT&CK release before citing them in a report.

Defense Evasion (TA0005)

  • T1027 Obfuscated Files or Information — see also analyzing-malware, analyzing-binaries
  • T1140 Deobfuscate/Decode Files or Information — see also analyzing-malware, analyzing-binaries
  • T1620 Reflective Code Loading — see also analyzing-malware

Detection content for any of these: engineering-detections. Proactive search: hunting-threats. Post-compromise: responding-to-incidents.

References

  • analyzing-binaries — disassembler workflow for the decoded stage
  • analyzing-malware — sample triage, containment, family ID, and the dropped stage
  • analyzing-memory-images — carving shellcode out of a memory dump first
  • writing-yara-rules — authoring a durable signature for the decoder or decoded stage
  • engineering-detections — turning a decoder/resolver signature into deployed rules
  • reporting-security-findings — writing up the analysis
  • scdbg, speakeasy, Unicorn, Qiling for emulation
  • radare2/rizin (rasm2/pd), Cutter for static disassembly of raw blobs
  • x64dbg/x32dbg with blob_runner / shellcode2exe / jmp2it to run under a debugger

Frequently asked questions

What does the Analyzing Shellcode AI skill do?

Analyze raw shellcode and position-independent code — extracting the bytes, guessing architecture, disassembling at the right base, decoding self-decoder stubs, resolving hashed Windows APIs, emulating to the decoded stage, and pulling C2 and stage IOCs. Use when handed a raw blob of position-independent code, an extracted payload with no PE/ELF header, shellcode lifted from an exploit or a macro/loader, a `\x`-escaped or base64 buffer, a suspected Cobalt Strike or Metasploit stager, or an egg-hunter.

Why use Analyzing Shellcode on TypingMind?

Because you install it once and use it with any model. Analyzing Shellcode is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Analyzing Shellcode in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/trilwu/secskills/tree/main/secskills-defense/skills/analyzing-shellcode. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Analyzing Shellcode?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Analyzing Shellcode?

As many as you like. As long as a model supports skills, you can use Analyzing Shellcode with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Analyzing Shellcode AI skill free?

Yes. It is published on GitHub by trilwu under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇