
javascript-obfuscator(obfuscator.io) 출력을 디오브푸스케이트하고, AST 변환, 정적 디코딩, goja 샌드박스 폴백을 사용해 JavaScript를 언미니파이하는 Go CLI.
jd는 javascript-obfuscator(obfuscator.io)의 출력을 난독화 해제하고 JavaScript를 압축 해제하는 Go 도구입니다. webcrack과 synchrony의 Go 포팅 버전입니다.
javascript-obfuscator 출력을 감지하고 복원합니다:
var/const/let)20개 이상의 가독성 변환:
| 변환 | 예시 |
|---|---|
| computed-properties | console["log"] → console.log |
| merge-strings | "a" + "b" → "ab" |
| unminify-booleans | !0 → true, !1 → false |
| number-expressions | 1 + 2 → 3 |
| void-to-undefined | void 0 → undefined |
| raw-literals | 0x1 → 1 |
| sequence | a(), b(), c() → 개별 문장 |
| split-variable-declarations | var a=1, b=2 → var a=1; var b=2 |
| block-statements | 단일 문장 본문을 { }로 감싸기 |
| logical-to-if | a && b() → if (a) b() |
| ternary-to-if | a ? b() : c() → if (a) b() else c() |
| merge-else-if | else { if (...) } → else if (...) |
| for-to-while | for(;;) → while(true) |
| yoda | 5 === x → x === 5 |
| infinity | 1/0 → Infinity |
| invert-boolean-logic | !(a == b) → a != b |
| unary-expressions | 문장 수준에서 무의미한 void/!/typeof 제거 |
| remove-double-not | !!true → true |
goja의 파서는 ES 모듈 구문(import/export)을 지원하지 않습니다. jd는 import/export 문을 미리 추출하고, 나머지 스크립트를 파싱한 뒤 변환을 실행하고, 출력 앞에 해당 문을 추가합니다. 동적 import() 표현식은 import 선언과 올바르게 구분됩니다.
goja가 완전히 파싱할 수 없는 파일(예: 정규식을 많이 사용하는 Monaco 에디터 언어 정의)의 경우, jd는 소스 수준 포매팅으로 폴백합니다: 토크나이저가 정규식 리터럴, 문자열, 주석을 그대로 보존하면서 세미콜론을 기준으로 분할합니다.
brew install ejfkdev/tap/jd
GitHub Releases에서 다운로드하세요 (Linux/macOS/Windows, x86_64/ARM64).
go build -o jd .
# 파일 난독화 해제 (stdout으로 출력)
jd obfuscated.js
# 파일로 쓰기
jd obfuscated.js -o cleaned.js
# stdin에서 읽기
cat obfuscated.js | jd -
# 디렉터리 처리 — 모든 .js/.mjs/.cjs 파일을 재귀적으로 처리하고,
# 디렉터리 트리를 출력 디렉터리에 미러링합니다.
jd src/ -o dist/
# 기본 출력을 사용하는 디렉터리 모드: <input>-deobfuscated 생성
jd src/
# 파일 확장자 지정
jd src/ -o dist/ --ext .js,.mjs
# 병렬 처리 (N 워커, 기본값: CPU 수)
jd src/ -o dist/ -j 8
# 비코드 파일 건너뛰기 (처리된 JS만 출력)
jd src/ -o dist/ --copy-noncode=false
# 난독화 해제만 하고 압축 해제는 건너뛰기
jd --unminify=false obfuscated.js
# 경고와 함께 JSON 출력
jd --json obfuscated.js
| 플래그 | 기본값 | 설명 |
|---|---|---|
-o, --output | stdout | 출력 파일 또는 디렉터리 (디렉터리 기본값: <input>-deobfuscated) |
--deobfuscate | true | obfuscator.io 난독화 해제 실행 |
--unminify | true | 가독성 변환 실행 |
--sandbox | auto | 디코더 실행: auto (정적 후 샌드박스), only (항상 샌드박스), off (정적만) |
--timeout | 10s | 파일당 샌드박스 타임아웃 |
--ext | .js,.mjs,.cjs | 디렉터리 모드에서 처리할 파일 확장자 |
-j, --workers | 0 (=CPU) | 디렉터리 모드의 병렬 워커 수 |
--copy-noncode | true | 비코드 파일을 출력 디렉터리에 복사 |
-v, --verbose | false | stderr에 진단 정보 출력 |
--json | false | {code, warnings} JSON 출력 |
jd/
├── main.go # CLI 진입점
├── internal/
│ ├── cli/ # cobra CLI (i18n: 영어/중국어)
│ ├── deobfuscator/ # 최상위 파이프라인 + ES 모듈 전처리
│ ├── jsast/ # AST 워커 (Cursor, Replace, Remove, Clone)
│ ├── codegen/ # AST → JavaScript 프린터 (pretty/compact)
│ ├── scope/ # 렉시컬 스코프 및 바인딩 분석
│ ├── sandbox/ # 디코더 실행을 위한 goja VM 래퍼
│ ├── decoder/ # 정적 디코딩: Base64/RC4/로테이션
│ ├── deobfuscate/ # 문자열 배열/로테이터/디코더 감지 + 변환
│ ├── unminify/ # 20개 가독성 변환 + 고정점 실행기
│ └── transform/ # Transform 추상화 + ApplyFixpoint
└── testdata/samples/ # 테스트 픽스처
parse → splitModuleStatements → deobfuscate → unminify → generate
IgnoreRegExpErrors를 사용하는 goja 파서; 파싱할 수 없는 파일은 소스 수준 포매팅으로 폴백디코더는 2단계 접근 방식을 사용합니다:
Interrupt 타임아웃, SetMaxCallStackSize, 결정적 rand/time, setInterval/setTimeout 없이 goja VM에서 실행 — 정적 경로가 디코딩할 수 없는 사용자 정의 인코더를 처리.MIT