LLM Destekli JS Tersine Mühendisliği: Yapay Zekâya Verebileceğiniz 4 İş ve Devredemeyeceğiniz 3 İş

Son Güncelleme: 2026-07-30 10:17:09

Webpack’in paketlediği bir imza SDK’sını açtığınızda karşınıza tanıdık bir manzara çıkar: tüm değişkenler a ya da _0x3f2b, kontrol akışı düzleştirilmiş, string sabitleri ise indekslenerek erişilen bir dizide saklanmış. Hedef net: Bu kodu, çıktısı orijinalle bayt bayt aynı olan bağımsız bir implementasyona dönüştürmek.

Bugün çoğu kişinin ilk refleksi paketin tamamını bir büyük dil modeline verip “bu kod ne yapıyor?” diye sormak oluyor. Genellikle daha ilk adımda sorun da burada başlıyor. Model gayet ikna edici görünen bir açıklama üretiyor, siz de buna göre kodu yazıyorsunuz; fakat sonuç hedef çıktıyla tek bir bayt bile örtüşmüyor.

Mesele modelin yetersiz olması değil; iş bölümü yanlış kuruluyor. Deobfuscation çalışmalarında model örüntü tanıma ve hipotez üretme konusunda güçlü, olgusal doğrulama konusunda ise zayıftır. Bit işlemleri yığınının altında saklanan kriptografik ilkelin iskeletini fark edebilir; ama teşhisinin doğru olup olmadığını söyleyemez. Buna yalnızca testleriniz karar verebilir.

Aşağıdaki dört aşamalı akış bu ayrım üzerine kurulu. Ayrıca modeli hiç dahil etmediğim üç işi de ele alıyor.

1. aşama: Mekanik ayrıştırmayı modele bırakmayın

İlk düşünce genellikle “context window çok büyük, her şeyi tek seferde verelim” oluyor. Yapmayın. Bunun iki temel nedeni var.

İlki maliyet ve dikkat israfı. Obfuscate edilmiş bir bundle’ın büyük bölümü çoğu zaman polyfill’lerden, runtime shim’lerinden ve hedefinizle ilgisi olmayan iş modüllerinden oluşur. Bunların tümünü bağlama taşırsınız; karşılığında modelin dikkati seyrelmiş olur.

İkincisi daha kritik: bağlam büyüdükçe halüsinasyonun yerleşebileceği alan da genişler. Model, birbiriyle ilgisiz iki modülün özelliklerini birleştirip size kendi içinde tutarlı ama kodda hiç var olmayan bir sonuç sunabilir. Bu hata türünü yakalamak, açıkça anlamsız bir cevabı ayıklamaktan çok daha zordur.

Ayrıştırma deterministik bir iştir. Bunu script ile yapın:

  • Bundle’ı modüllere ve fonksiyonlara ayırmak, ardından bunları scope’a göre indekslemek için bir AST aracı kullanın: @babel/parser, acorn gibi;

  • Tüm sayısal literal’leri ve string sabitlerini çıkarın; frekanslarına ve bit genişliklerine göre gruplayın;

  • Çağrı grafiğini oluşturun; in-degree değeri 0 olan düğümleri giriş noktası, out-degree değeri 0 olanları da yaprak ilkel olarak işaretleyin;

  • Bit işlemi yoğunluğu olağandışı yüksek fonksiyonları bulun. Bir fonksiyon içinde ^, >>>, <<, & işlemlerinin kümelenmesi genellikle algoritmik çekirdeğe işaret eder.

Modele göstermek istediğiniz asıl parçalar bu yaprak ilkellerdir. Çoğunlukla birkaç düzine satırdan oluşurlar, üst katman durumuna bağlı değildirler ve giriş/çıkış sınırları nettir. Başvurduğu sabitlerle birlikte 40 satırlık bir yaprak fonksiyon, modelin güvenilir biçimde ele alabileceği ideal ölçektedir.

Bu aşamanın sonunda elinizde bir “aday ilkel listesi” bulunmalı: Her kayıtta fonksiyon gövdesi, başvurduğu sabitler ve çağrıldığı yer yer alır. Bundan sonraki her model çağrısı, bu listedeki tek bir kayıt üzerinde çalışır.

2. aşama: Algoritma ailesini modele tespit ettirin

Modelin gerçekten vazgeçilmez olduğu aşama burasıdır.

Kriptografik algoritmaların ve kodlama yöntemlerinin güçlü parmak izleri vardır: belirli sabitler, belirli shift miktarı kombinasyonları, belirli döngü yapıları. İnsan bunları yıllar içinde edindiği deneyimle tanır; model ise herkese açık implementasyonların neredeyse tamamını gördüğü için bu eşleşmeleri doğal biçimde yakalar.

Parmak izinin neye benzediğini göstermek için birkaç açık örnek:

  • 0x811c9dc5 ile 0x01000193 birlikte, 32 bit FNV-1a hash algoritmasının offset basis ve prime değerleridir. Bunlar, ortak yazarlardan Landon Curt Noll’un yönettiği FNV referans sayfasında standart değerler olarak yer alır; decimal karşılıkları 2166136261 ve 16777619’dur;

  • 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, ASCII "expand 32-byte k" metninin little-endian word’leridir. Bunlar ChaCha20 başlangıç durumunun sabitleridir ve RFC 8439 §2.3 içinde listelenir;

  • a += b; d ^= a; d = rotl(d, 16); c += d; b ^= c; b = rotl(b, 12); biçimindeki yapı ve 16/12/8/7 rotasyon miktarları ChaCha20 quarter round’un imzasıdır. Aynı aileden Salsa20 ise 7/9/13/18 kullanır; yalnızca bu dört sayı iki algoritmayı ayırmaya yeter;

  • 0xd76aa478 ile başlayan 64 elemanlı sabit tablosu, MD5’in T-tablosudur (RFC 1321 §3.4);

  • 0x63, 0x7c, 0x77, 0x7b ile başlayan 256 baytlık tablo AES S-box’tır (FIPS 197, Tablo 4);

  • 0xEDB88320, yansıtılmış CRC-32 polinomudur; gzip spesifikasyonu RFC 1952 de bu değeri kullanır.

İşin tamamı soruyu nasıl sorduğunuzda bitiyor. “Bu kod ne yapıyor?” sorusu size düz metin açıklama döndürür. Oysa ihtiyacınız olan şey doğrulanabilir ve yapılandırılmış bir değerlendirmedir. Bu nedenle prompt, üç parçalı bir cevap zorlamalıdır: adaylar, kanıtlar ve karşı kanıtlar.

Below is a leaf function extracted from an obfuscated bundle, together with
every numeric constant it references.

<function>
{{function body}}
</function>

<constants>
{{constant list, with locations}}
</constants>

Answer in the following structure. Do not write prose.

1. Candidate algorithm families (at most 3, ranked by likelihood)
   For each: the name, and which class it belongs to
   (hash / stream cipher / block cipher / encoding / compression / checksum)

2. Supporting evidence
   Every piece of evidence must point to a specific constant value or a
   specific line above. "Structurally similar" and other unverifiable
   phrasing is not allowed.

3. Counter-evidence and deviation points
   If this were the standard implementation of that algorithm, what should
   appear here but doesn't? What appears that a standard implementation
   would never contain? Are these deviations a "variant," or "I got it wrong"?

4. The minimal test to decide this hypothesis
   Give 3 concrete inputs and, if the hypothesis holds, the shape of the
   output each should produce. Include boundary cases.

Bu prompt’un asıl değeri 3. bölümde ortaya çıkar. Standart implementasyondan sapmalar, kodun değiştirildiği noktaları doğrudan gösterir: özel bir alfabe, değiştirilmiş bir sabit ya da farklılaştırılmış round sayısı gibi. Yeniden yazım sırasında asıl uğraşmanız gereken yerler de yalnızca bunlardır; geri kalan her şeyi herkese açık implementasyondan alabilirsiniz. Modelin tüm sapmaları görünür kılmasını sağlamanın ayrıntıları algoritma parmak izi tespiti yazısında yer alıyor.

4. bölüm ise modelin değerlendirmesini doğrudan sıradaki testlere dönüştürür; böylece ek bir soru-cevap turundan kurtulursunuz.

Bu aşamada standart dışı encoding’lerle de sık karşılaşılır. Kontrol mekaniktir: 65 karakterlik alfabe — 64 karakter ve bir padding sembolü —, 6 bitlik gruplama, ceil(n/3)*4 çıkış uzunluğu; bunlar Base64 ailesinin işaretleridir. Alfabenin sırası değişmişse özel bir tablo kullanılıyordur. Benzer şekilde, 256’dan başlayan ve doldukça büyüyen bir sözlük ile sözlük büyüdükçe genişleyen çıktı kodu LZW’yi gösterir. Bunların tamamını, tek satır kod okumadan yalnızca giriş/çıkış uzunluğu ilişkilerinden doğrulayabilirsiniz.

3. aşama: Hipotezi diferansiyel testle sınayın

Model size bir hipotez verdi. Siz testini yazana kadar bu yalnızca bir cümledir.

Burada kısa yol yok. Tüm akışta halüsinasyonları durduran tek kapı da budur; neden tek kapı olduğu diferansiyel test yazısında ayrıntılı biçimde anlatılıyor. Orijinal implementasyonu kara kutu kabul eder, yeniden yazdığınız sürümü onunla vaka vaka karşılaştırırsınız:

# Differential-test skeleton: original as black box, rewrite compared case by case
CASES = [
    b"",                      # empty input: exposes initial state and padding logic
    b"\x00",                  # single zero byte
    b"\xff",                  # single high byte: checks sign-bit handling
    b"a" * 63,                # one below the block boundary
    b"a" * 64,                # exactly one block
    b"a" * 65,                # one above: checks padding and carry
    bytes(range(256)),        # full byte coverage: checks alphabet mapping
]

for case in CASES:
    assert rewritten(case) == blackbox(case), case.hex()

Pratikte işinize yarayacak birkaç kural var:

Sınır geçişleri en fazla bilgiyi taşır. Blok algoritmaları padding mantıklarını en açık şekilde 64n ve 64n±1 noktalarında ele verir. Tüm çalışmada yalnızca tek vaka başarısız oluyorsa, o girdinin uzunluğu hangi katmanın hatalı olduğunu doğrudan gösterir.

Aynı girdiyi iki kez çalıştırın. Sonuçlar farklıysa implementasyon rastgele sayı veya timestamp karıştırıyordur. Bu durumda enjeksiyon noktasını bulup dışarıdan override edilebilir hâle getirmelisiniz; aksi hâlde diferansiyel karşılaştırma yapmanız mümkün olmaz. İmza mantığını yeniden yazarken en sık rastlanan tıkanma noktası budur: Algoritma yanlış değildir, yalnızca entropy kaynağı henüz ayrıştırılmamıştır.

Katmanları tek tek soyun; tüm çıktıyı birden karşılaştırmayın. Önce en içteki hash katmanını eşleştirin, o geçtikten sonra encoding katmanına, ardından assembly katmanına ilerleyin. Tüm çıktı uyuşmadığında hatanın nerede olduğunu bilemezsiniz; katmanlara ayırdığınızda ise başarısız olan ilk katman sorunun yerini gösterir.

Sabit test vektörlerini fixture olarak commit edin. Bilinen girdi → bilinen çıktı tablosu, upstream güncellemesinden sonra “ben mi yanlış yazdım, yoksa onlar mı değiştirdi?” sorusunu hızlıca yanıtlamanızı sağlar. Bu fixture’ın değeri zamanla yalnızca artar.

Bu aşamada modelin görevi test vakaları üretmek ve farkları açıklamaktır; doğruyla yanlışa hükmetmek değil. Doğruya ve yanlışa karar veren şey assert ifadesidir.

4. aşama: Taşıma katmanına göre kademeli geri dönüş planlayın

Tüm hipotezler testlerden geçtikten sonra bunu uzun vadede çalışacak bir koda dönüştürürsünüz. Burada bir öncelik sırası vardır; katman ne kadar yukarıdaysa onu elde etmek için o kadar fazla çaba harcamalısınız:

  1. Hedef dilde native yeniden yazım. Orijinal runtime’dan tamamen kopuk, yalnızca standart kütüphaneye bağımlı bir implementasyon. Ek süreç yoktur, ek bağımlılık yoktur ve CI entegrasyonu temizdir.

  2. Minimal bir parçayı çalıştıran yerel JS motoru. Bazı mantıkları kısa vadede saflaştırmak fazla maliyetli olabilir. Bu durumda orijinal JS’in küçük bir bölümünü korur, yerel Node/V8 üzerinde çalıştırırsınız. Ancak JS motoru context’i thread-safe değildir; derlenmiş tek bir context’e birden çok thread’den erişim kilitlenmelidir:

class Signer:
    def __init__(self, source: Path) -> None:
        self._context = execjs.get("Node").compile(source.read_text())
        self._lock = threading.Lock()  # V8 context is not thread-safe

    def call(self, fn: str, *args):
        with self._lock:
            return self._context.call(fn, *args)
  1. Pasif browser bridge. Yalnızca gerçek bir sayfa runtime’ından elde edebileceğiniz state için şimdilik tek seçenek tarayıcı olabilir. Bu geçici bir çözümdür; arayüz notlarında açıkça işaretleyin ve varsayılan implementasyon hâline gelmesine asla izin vermeyin.

Bu geri dönüş sırasını proje kurallarınıza yazmakta fayda var. Her katman aşağı indiğinizde bağımlılık yüzeyi, hata modları ve dağıtım maliyeti bir büyüklük mertebesi artar. 1. katman saf bir fonksiyondur; 3. katman ise oturumun canlı kalması için insan müdahalesi gerektiren harici bir süreçtir. Varsayılan olarak 1. katman için mücadele ederseniz, “şimdilik çalışsın yeter” yaklaşımının sessizce biriktirdiği uzun vadeli maliyetlerin çoğunu engellersiniz. Bu üç katmanın nasıl çizileceği ve pasif bridge’in kontrolden çıkmasının nasıl önleneceği saflaştırma merdiveni yazısında anlatılıyor.

Bir referans rakam vermek gerekirse: Dört aşamanın tamamından geçirilen obfuscate edilmiş bir imza SDK’sı, runtime’ın yerleşik crypto modülüne bağımlı, 600 satırın altında bağımsız bir implementasyona dönüşebilir. Bu sıkıştırma oranı modelin orijinal dosyayı “anlamasından” kaynaklanmaz. Algoritma ailesi doğru tanımlandığında kodun çok büyük kısmı zaten herkese açık implementasyondan doğrudan alınabilir.

Hangi aşamada hangi model kullanılmalı?

Dört aşama birbirinden tamamen farklı yetkinlikler ister. Baştan sona tek model kullanmak ya parayı ya da doğruluğu boşa harcamanıza neden olur:

Aşama

Gerçekte gereken yetkinlik

Tercihim

model id

Ayrıştırma sonrası yapısal haritalama

Uzun context; bir modülün çağrı grafiğini tek seferde okuyabilme

Kimi K3

kimi-k3

Algoritma ailesi tespiti ve karşı kanıtlar

Güçlü muhakeme; sapmaları bulma ve kendi sonucuna itiraz edebilme

Claude Opus 5

claude-opus-5

Toplu sembol yeniden adlandırma, yorum tamamlama

Düşük maliyet; yüksek eşzamanlılıkla yüzlerce çağrı çalıştırabilme

Claude Sonnet 5

claude-sonnet-5

Fark atfetme: Test başarısız olduğunda diff okuma

Orta seviye muhakeme; belirli baytlar üzerinden açıklama yapabilme

GPT-5.6 Sol

gpt-5.6-sol

Özellikle 2. aşama üzerinde durmaya değer. Algoritma ailesi tespiti, model değiştirmenin sonucu gözle görülür biçimde etkilediği tek adımdır. Çünkü burada ölçülen şey tam olarak şudur: Model kaç tane herkese açık implementasyon gördü ve kendi sonucuna karşı argüman üretmeye ne kadar istekli? Aynı yaprak fonksiyonda daha zayıf bir model size kendinden emin biçimde yanlış cevap verirken, daha güçlü bir model karşı kanıtlar bölümünde kendi adayını eleyebilir.

Aradaki fark için bana inanmak zorunda değilsiniz; kendiniz deneyin. Protokol şu:

  1. Kendi obfuscate edilmiş bundle’ınızdan 3 yaprak fonksiyon seçin. Bunların en az 1 tanesinin yanıtını önceden biliyor olun; bu, kontrol vakasıdır.

  2. 2. aşamadaki üç parçalı prompt’u kullanarak aynı girdiyi ayrı ayrı claude-opus-5 ve gpt-5.6-sol modellerine verin.

  3. Yalnızca iki şeye bakın: Aday algoritma ailesini tutturdu mu; ayrıca karşı kanıtlar bölümü gerçekten kendi sonucuna itiraz mı ediyor, yoksa adayı farklı kelimelerle tekrar mı anlatıyor?

  4. Seçim ölçütünüz karşı kanıtlar bölümünün kalitesi olsun. Çünkü bu, 3. aşamada yazacağınız işe yaramaz test sayısını doğrudan belirler.

3. ve 4. aşamalarda model seçimi neredeyse önemini kaybeder; çalışan herhangi bir model yeterlidir. 1. aşamada ise kendi parçalı retrieval sisteminizi yazmamak için uzun context’li bir model tercih edilir.

Asıl sorun model seçimi değil, model değiştirme maliyeti

Üç farklı sağlayıcıdaki dört model demek; üç SDK, üç kimlik doğrulama yöntemi ve üç hata formatı demektir. Biraz tasarruf etmek için istemcinizi yeniden yazmak çoğu zaman değmez. Zaten insanların her iş için tek model kullanmaya başlamasının temel nedeni de budur.

AIReiter bu katmanı ortadan kaldırıyor: Tek anahtar, OpenAI uyumlu tek arayüz, arka planda dört model. Geçiş yapmak için isteğin gövdesindeki model alanını değiştirmeniz yeterli.

# Algorithm-family ID: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<Stage 2 prompt + leaf function + constants>"}]
  }'

# Bulk symbol renaming: change the model field, leave everything else
#   "model": "claude-sonnet-5"

Halihazırda OpenAI SDK kullanıyorsanız base_url değerini https://aireiter.com/api/v1 olarak ayarlayın; başka hiçbir şeyi değiştirmeniz gerekmez. Anthropic SDK kullanıyorsanız aynı anahtarla POST /api/v1/messages endpoint’ine istek atın.

Fiyat tarafında Claude modelleri liste fiyatına göre %30 indirimli, GPT modelleri ise yarı fiyatına sunuluyor. Bu iş akışında bunun etkisi ilk bakışta göründüğünden büyük: 3. aşamadaki toplu sembol yeniden adlandırma kolayca yüzlerce çağrıya ulaşır; 1. aşamadaki uzun context geçişi ise tek bir girdi için birkaç yüz bin token tüketir. Maliyetin büyük kısmı bu iki kalemde oluşur ve indirim doğrudan en pahalı bölüme uygulanır.

Modele devretmemeniz gereken 3 şey

Bir: Nihai implementasyonu doğrudan modelden istemeyin. Modele “tam yeniden yazımı ver” dediğinizde ortaya eksiksiz görünen, çalışan ama ince sapmalar barındıran kod çıkar. Sapmayı bulmanız da zordur; çünkü bu kodu katman katman siz doğrulamadınız. Doğru kullanım, her ilkel için modelden bir hipotez almak; bunları tek tek doğrulamak ve nihai yapıyı kendiniz kurmaktır. Daha yavaştır, ama her satırın neden o şekilde yazıldığını bilirsiniz.

İki: Sonucun doğru olup olmadığına modeli karar verdirmeneyin. “Bu implementasyonun doğru olduğunu onaylar mısın?” sorusu çıkmaz sokaktır; model sizinle aynı fikirde olmaya eğilimlidir. Doğru ve yanlışın tek otoritesi diferansiyel testtir. Model doğru diyor ama test başarısız mı? Test kazanır. Model yanlış diyor ama tüm testler geçiyor mu? Yine test kazanır.

Üç: Uyumluluk kararını modele bırakmayın. Hedefe müdahale edip edemeyeceğiniz, vardığınız sonucu yayınlayıp yayınlayamayacağınız ve elde ettiğiniz verileri nasıl kullanabileceğiniz; bunların hepsi bulunduğunuz yargı alanına, hedefin şartlarına ve amacınıza bağlıdır. Modelin bunların hiçbiri hakkında olgusal bir dayanağı yoktur; verdiği cevap yalnızca daha önce gördüğü sorumluluk reddi metinlerinin tonunu taklit eder. Bu karar size ya da gerçek hukuk danışmanınıza aittir.

Sonuç

Bu tür çalışmalarda modelin rolü nettir: Herkese açık algoritma implementasyonlarını görmüş bir örüntü tanıyıcıdır ve size saniyeler içinde aday hipotezler sunabilir. Cevabın kaynağı değildir; doğrulayıcı da değildir.

İş akışının iskeleti, yapay zekâ kullanılıp kullanılmadığından bağımsızdır: mekanik ayrıştırma, hipotez, doğrulama, saflaştırma. Bu dört adım modeller ortaya çıkmadan önce de vardı. Modelin kısalttığı tek bölüm, referansları günlerce taramaktan dakikalara inen “hipotez” aşamasıdır. Diğer üç aşamanın maliyeti ise eskiden neyse bugün de odur.

Bu dört aşamadaki model çağrılarını script’lere sabitlediğinizde tüm iş akışı hızlanır. Geriye kalan tek sürtünme model değiştirmektir; bu da birleşik tek arayüz üzerinde modelinizi seçerek çözülen bir altyapı problemidir.