协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
re-shellcode【开发与工程】Shellcode 分析:提取、解码循环、模拟执行。 触发词:shellcode、shell code、位置无关、解码循环、恶意载荷
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
所有工具先验证再使用。本技能静态解码可免沙箱;模拟执行默认沙箱内进行([[re-analyze/platform-tips]] 最高原则)。
apt install python3 python3-pip / dnf install python3 python3-pip / pacman -S python python-pip(多数自带)brew install python(或 Xcode CLT 自带);Windows: python.org 安装包(勾选 Add to PATH)或 choco install python;WSL 内 Linux 版python3 --versionpip install unicorn(Python 3)python3 -c "import unicorn; print(unicorn.__version__)"pip install capstonepython3 -c "import capstone; print(capstone.__version__)"binwalk --versionapt install xxd(新版独立包,旧版在 vim-common);Fedora: dnf install xxd(F38+ 独立子包,旧版 vim-common);Arch: pacman -S xxdFormat-Hexxxd -vapt install binutils / dnf install binutils / pacman -S binutils(多数发行版预装)objdump --versionRE_DECOMPILER 或环境选Nypreo/scdbg releases 下载(Windows 可执行文件;Linux 可在 Wine 下运行,或直接用 Unicorn 方案替代)scdbg.exe -f blob.bin 能输出反汇编与模拟结果apt install metasploit-framework);Fedora: 添加 Rapid7 RPM 仓库后 dnf install metasploit-framework;Arch: pacman -S metasploit(extra 仓库,6.x)msfvenom -l encoders | head -5(msfvenom 可独立使用,不依赖 msfconsole 的 PostgreSQL)x86/shikata_ga_nai)生成样本,对照解码循环指令形态按顺序执行,每步产物(blob/解码结果/模拟输出)存档 sha256 + 路径([[re-ioc]] 证据链要求)。
提取 shellcode blob(文件/内存/流量三来源):
# 文件内嵌: 从宿主文件里扫嵌入段
binwalk sample.bin
binwalk -e sample.bin # 按签名自动提取
# 定长段提取(已知偏移):
dd if=sample.bin of=blob.bin bs=1 skip=<off> count=<len>
# ELF .text 段导出(载荷伪装成代码段时):
objcopy --dump-section .text=blob.bin sample.elf
# 内存: 沙箱内 gcore 转储后按特征找(见 [[re-memdump]])
# 流量: tshark 提取 pcap 中的载荷字节(见 [[re-netcap]])
tshark -r c2.pcap -Y 'http' -T fields -e http.file_data > payload.hex
xxd -r -p payload.hex blob.bin
file blob.bin + 熵判断,乱码先假设有解码层(步骤 3)定位入口(0xEB 短跳 / 反汇编扫描):
xxd blob.bin | head -20 # 头部特征
# 入口候选: 首字节 0xEB(jmp short,跳到解码器)、0xE8(call,自定位取 EIP)
objdump -D -b binary -m i386 blob.bin | head -40 # 32 位 x86 裸反汇编
objdump -D -b binary -m i386:x86-64 blob.bin | head -40 # 64 位
EB xx 短跳(跳到解码循环,入口前留解码器空间)② E8 00 00 00 00 + pop reg(call/pop 自定位,拿当前 EIP)③ 解码循环直接开头rizin -a x86 -b 32 blob.bin 进入后 s <候选偏移> + pd 20 逐段看(见 [[re-radare2]]);Ghidra 导入选 Raw Binary(x86:LE:32:default),在候选入口 L 标函数解码循环分析(XOR/ROL 自解码):
# 特征指令(反汇编中找):
# XOR byte ptr [reg+disp], imm 单字节 XOR 解码
# ROL/ROR byte ptr [reg], imm 循环移位解码
# ADD/SUB byte ptr [reg], imm 加减常量解码
# LOOP / JECXZ 循环控制(计数在 ECX/RCX)
data = bytearray(open('blob.bin','rb').read())
key, start, length = 0x55, 0x00, len(data) # 参数来自反汇编
for i in range(start, start + length):
data[i] ^= key
open('decoded.bin','wb').write(data)
x86/xor_dynamic 等编码器生成样本,比较解码循环指令形态,确认识别没走偏(见坑 2)模拟执行(Unicorn 无环境运行、hook 系统调用):
from unicorn import *
from unicorn.x86_const import *
blob = open('blob.bin','rb').read()
mu = Uc(UC_ARCH_X86, UC_MODE_32) # 按步骤 2 确认的架构选模式
CODE, STACK = 0x1000, 0x3000
mu.mem_map(CODE, 0x1000); mu.mem_map(STACK, 0x1000)
mu.mem_write(CODE, blob)
mu.reg_write(UC_X86_REG_ESP, STACK + 0x800) # 设栈(不设必崩)
def hook_code(uc, addr, size, user):
if addr in API_ADDRS: # 步骤 5 定位的 API 调用点
uc.reg_write(UC_X86_REG_EIP, addr + size) # 跳过 stub
print("hit api", hex(addr))
mu.hook_add(UC_HOOK_CODE, hook_code)
try:
mu.emu_start(CODE, 0x100000) # until 给大上限,非法访问即终止
except UcError as e:
print("stop:", e) # 缺 API/越界 = 预期终止点
open('decoded.bin','wb').write(mu.mem_read(CODE, len(blob))) # 解码产物
hook_mem_write 可记录解码产物落点(见 [[re-emulation]] 步骤 4)与宿主交互面(API 解析:kernel32 遍历、hash 查找):
def djb2(s):
h = 5381
for c in s:
h = ((h << 5) + h + c) & 0xffffffff
return h
for name in ['LoadLibraryA','VirtualAlloc','VirtualProtect',
'WriteProcessMemory','CreateThread','WinExec']:
print(name, hex(djb2(name.encode())))
WINAPI/__stdcall(callee 清栈、收尾为 RET n),程序自身默认 __cdecl、成员函数 __thiscall(只有 /Gz 才把默认改成 __stdcall),按符号修饰 / 调用者栈平衡 / ECX-EDX 使用判定;64 位用统一 x64 ABI(前四整型/指针参数 RCX/RDX/R8/R9 + 32 字节 shadow space,类 fastcall,不是 __fastcall)——决定参数寄存器/栈顺序与返回值位置(坑 3)RE_DECOMPILER 选)s <偏移>、xref 看相对偏移),Ghidra 用 Raw Binary 导入并从候选入口标函数,别拿绝对地址对齐call kernel32... 或 syscall 时 UcError 终止,控制流走不下去;原因——Unicorn 无 OS,内核与 DLL 都不存在;对策——步骤 5 先解析 API hash 还原真实 API 集合,按功能 stub(hook 调用点返回假值 + 记录参数),必要时 hook_mem 补内存映射(VirtualAlloc 返回的"新页"先 mem_map 好)