EresusSecurity
Araştırmalara Dön
Offensive Security

JavaScript Obfuscation Tersine Mühendislik: Pratik Deobfuscation Rehberi

Yiğit İbrahim SağlamOfansif Güvenlik Uzmanı
17 Mayıs 2026
6 dk okuma
Technical GuideApplication Security

JavaScript obfuscation, hedefin frontend bundle'ında gizlenen iş mantığı ile senin aranda duran ilk engeldir. Bu rehber onu nasıl sökeceğini gösteriyor — basit eval(unescape(...)) sarmalayıcısından AST cerrahisi gerektiren çok aşamalı JScrambler çıktısına kadar.

Bu bir araç listesi yazısı değil. Aşağıdaki her teknik neden işe yaradığını ve ne zaman kullanılacağını içeriyor.

Hedefler Neden Obfuscate Ediyor?

En yaygın sebepler:

  • SPA bundle'larına gömülü API key ve endpoint'leri gizlemek
  • Ücretli içerik mantığını korumak (DRM, lisans kontrolleri)
  • Production bundle'larda kalan admin veya debug route'ları saklamak
  • Scraping açısından hassas ürünlerde anti-otomasyon
  • Dolandırıcılık tespiti katmanlarındaki parmak izi mantığını korumak

Pentest açısından her biri bekleyen bir bulgu kategorisi.

Önce Obfuscation Türünü Tanı

Araç seçmeden önce ne ile karşı karşıya olduğunu belirle. Yanlış obfuscator'a yanlış araç uygulamak saatleri mahveder.

Pattern 1 — String Array Rotation (obfuscator.io varsayılanı)

var _0x3a2b = ['log', 'Merhaba', 'Dünya'];
var _0x1f4c = function(_0x3a2b, _0x1f4c) { /* rotation */ };
(function(_0x3a2b, _0x1f4c) { /* shuffle */ })(_0x3a2b, 0x1b3);

İşaret: Hex değişken adları (_0x...), dosyanın başında büyük dizi, kendini çağıran shuffle fonksiyonu. obfuscator.io ve pek çok ticari aracın çıktısı.

Pattern 2 — Control Flow Flattening

var _0xabc = '3|1|4|0|2'.split('|'), _0xi = 0x0;
while (true) {
  switch (_0xabc[_0xi++]) {
    case '0': doThing0(); continue;
    case '1': doThing1(); continue;
  }
  break;
}

İşaret: String güdümlü dağıtımlı büyük while(true) + switch bloğu. Her fonksiyon gövdesi durum makinesiyle değiştirilmiş.

Pattern 3 — Dead Code Injection

Meşru mantık, yüzlerce erişilemeyen dal ve anlamsız atama arasına gömülmüş. Tespit sinyali: görünen işlevselliğe kıyasla aşırı büyük dosya boyutu.

Pattern 4 — eval / Function Constructor zincirleri

eval(function(p,a,c,k,e,d){...}('...', 62, ...))

Klasik [p,a,c,k,e,r] (packer.exe) veya özel eval zincirleri. Eski numara, ama legacy PHP üretimi JS ve bazı WordPress eklentilerinde hâlâ yaşıyor.

Pattern 5 — Webpack + Terser (obfuscation değil, minified)

Obfuscate edilmemiş — sadece küçültülmüş. webcrack bunu tamamen çözer. AST cerrahisine zaman harcama.


Adım 1 — Önce Source Map Kontrol Et

Herhangi bir şey yapmadan önce:

# Bundle'da inline source map yorumu var mı?
grep "sourceMappingURL" app.bundle.js

# .map dosyası herkese açık var mı?
curl -I https://hedef.com/static/js/main.chunk.js.map

# Yaygın konumlar
curl https://hedef.com/static/js/2.abc123.chunk.js.map
curl https://hedef.com/_next/static/chunks/pages/_app-abc.js.map

.map dosyasından 200 alırsan: oyun bitti, orijinal kaynak kodun var. İndir:

npx source-map-explorer app.chunk.js app.chunk.js.map

Ya da manuel çıkarım için:

node -e "
const m = JSON.parse(require('fs').readFileSync('app.chunk.js.map'));
m.sources.forEach((s,i) => {
  const path = require('path');
  const outPath = 'recovered/' + s.replace(/\.\.\//g,'_/');
  require('fs').mkdirSync(path.dirname(outPath), {recursive:true});
  require('fs').writeFileSync(outPath, m.sourcesContent[i] || '');
});
"

Production'da açık kalan source map'ler bir bulgudur: Bilgi İfşası — Kaynak Kod Kurtarma.


Adım 2 — Hızlı Otomatik Geçiş

Dosyaya manuel dokunmadan önce bunları çalıştır:

webcrack (webpack için en iyi)

npx webcrack app.bundle.js -o ./recovered/

Webpack bundle parçalama, chunk yeniden birleştirme, değişken yeniden adlandırma. Terser/webpack çıktısında çok iyi çalışır.

synchrony (obfuscator.io çıktısı için en iyi)

npx synchrony deobfuscate app.obfuscated.js

obfuscator.io string array rotation deseni için özel olarak yapılmış. Diziyi çözer, tüm referansları değiştirir, değişkenleri okunabilir adlarla yeniden adlandırır.

prettier ile okunabilirlik

npx prettier --parser babel app.obfuscated.js > app.pretty.js

Adım 3 — eval Zincirlerini Dinamik Olarak Çözme

Obfuscator, çalışma zamanında string'leri çözmek için iç içe eval() çağrıları kullandığında statik analiz çöker. Güvenilir yaklaşım: çalışma zamanında eval'i ele geçir.

Chrome DevTools snippet yaklaşımı

DevTools (F12) aç → SourcesSnippets → Yeni snippet:

// Tüm eval çağrılarını yakala ve çözülmüş içeriği logla
const origEval = window.eval;
window.eval = function(code) {
  console.log('=== EVAL YAKALANDI ===');
  console.log(code.substring(0, 500));
  // İsteğe bağlı: copy(code) ile tam string'i panoya gönder
  return origEval.call(this, code);
};

// Function constructor'ı da yakala
const origFunction = Function;
window.Function = function(...args) {
  console.log('=== Function() YAKALANDI ===');
  console.log(args);
  return origFunction(...args);
};

Snippet'i çalıştır, sonra sayfayı yenile. Dinamik olarak üretilen tüm kodlar çalışmadan önce console'da görünür.

String çözme fonksiyonlarını yakala

Çoğu obfuscator'ın tüm çağrıların yönlendirildiği tek bir merkezi çözme fonksiyonu vardır. Bul ve patch'le:

// Çözme fonksiyonunu belirledikten sonra (örn. _0x1f4c)
const orig = _0x1f4c;
window._0x1f4c = function(a, b) {
  const result = orig(a, b);
  console.log(`decode(${a}, ${b}) = "${result}"`);
  return result;
};

Console'da çalıştır. Artık her obfuscate edilmiş string referansı sayfa çalışırken düz metin eşdeğerini logluyor.


Adım 4 — AST Tabanlı Cerrahi (Control Flow Flattening için)

Kod yapısının kendisi tahrip edildiğinde (yalnızca string kodlama değil), AST manipülasyonu gerekir. Doğru araç: Babel.

Kurulum

npm install -g @babel/core @babel/parser @babel/traverse @babel/generator

Önce string dizisini çöz

// deobfuscate-strings.mjs
import parser from '@babel/parser';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
import fs from 'fs';

const code = fs.readFileSync('app.obfuscated.js', 'utf8');
const ast = parser.parse(code);

let stringArray = [];
traverse.default(ast, {
  VariableDeclarator(path) {
    if (
      path.node.id.name.startsWith('_0x') &&
      path.node.init?.type === 'ArrayExpression'
    ) {
      stringArray = path.node.init.elements.map(e => e.value);
    }
  }
});

// Tüm dizi erişim çağrılarını string değerleriyle değiştir
traverse.default(ast, {
  CallExpression(path) {
    if (path.node.callee.name?.startsWith('_0x') && 
        path.node.arguments.length === 1) {
      const idx = path.node.arguments[0].value;
      if (typeof stringArray[idx] === 'string') {
        path.replaceWithSourceString(JSON.stringify(stringArray[idx]));
      }
    }
  }
});

const output = generate.default(ast);
fs.writeFileSync('adim1-stringler-cozuldu.js', output.code);
console.log('Tamamlandı. String dizisi çözüldü.');

AST analizi için astexplorer.net

Obfuscate edilmiş kodu astexplorer.net adresine yapıştır, ayrıştırıcıyı @babel/parser olarak ayarla. Dönüştürme fonksiyonlarını inline yazabilir ve çıktının gerçek zamanlı güncellendiğini görebilirsin. Tam dönüşümü script haline getirmeden önce node şekillerini anlamak için vazgeçilmez.


Adım 5 — Kurtarılan Kodda Sır Avı

Okunabilir hale geldikten sonra ne aranır:

API key'leri ve token'lar

grep -E "(api[_-]?key|apikey|api[_-]?secret|access[_-]?token|bearer|Authorization)" \
  recovered/ -r -i --include="*.js"

Hardcoded admin route'lar

grep -E '"/admin|"/internal|"/debug|"/v[0-9]/admin' recovered/ -r --include="*.js"

AWS/GCP/Azure kimlik bilgileri

grep -E "(AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35})" \
  recovered/ -r --include="*.js"

Next.js SPA için gerçek vaka deseni

  1. curl https://hedef.com/_next/static/chunks/pages/index-abc.js.map → 200 → bitti
  2. Source map yoksa: tüm chunk'ları indir (_next/static/chunks/*.js)
  3. Her chunk'ta webcrack çalıştır — mantığın ~%70'ini okunabilir kılar
  4. Kalan obfuscate edilmiş chunk'lar için: her chunk'ta string dizisini bul → synchrony çalıştır
  5. _next/static/chunks/framework-*.js dosyalarını atla — polyfill'ler
  6. pages/ ve app/ chunk'larına odaklan — gerçek route mantığı orada

SSS

Pentest sırasında JavaScript deobfuscation yasal mı?

Evet. Test yetkisi verilen bir hedeften indirilen JavaScript'i okumak herhangi bir yargı bölgesinde yasadışı değil. Bu, HTML kaynağını okumakla aynı şey. Obfuscation'ın yasal koruması yok.

Otomatik araçlar her JS'yi tam olarak deobfuscate edebilir mi?

Hayır. Yüksek düzeyde özelleştirilmiş obfuscator'lar (JScrambler Enterprise, özel araçlar) manuel AST cerrahisi gerektirir. Otomatik araçlar yaygın desenleri (obfuscator.io, packer, webpack) güvenilir biçimde çözer.

.map dosyasının herkese açık olması her zaman bulgu mudur?

Açık kaynak projeler kasıtlı olarak source map yayınlar. Kapalı kaynaklı ticari bir hedef için ifşa olan source map her zaman bilgi ifşası olarak raporlanabilir.

Obfuscator müdahaleyi tespit eden self-defending kod kullanıyorsa ne yapılır?

Self-defending kod genellikle Function.prototype.toString veya hash kontrolleri aracılığıyla kendi kaynağında bütünlük denetimleri çalıştırır. Herhangi bir kod çalışmadan önce DevTools snippet düzeyinde bunları geçersiz kıl:

const orig = Function.prototype.toString;
Function.prototype.toString = function() {
  return orig.call(this).replace(/\s+/g, ' ');
};

Güvenlik Doğrulaması

Bu riski kendi sisteminizde test ettirdiniz mi?

Eresus Security; sızma testi, AI ajan güvenliği ve kırmızı takım operasyonlarıyla gerçek istismar kanıtı üretir.

Pilot test talep et

İlgili Araştırmalar