sıfırdan tersine mühendislik

Makine kodunu
okumayı öğren.

Hiç assembly görmediysen de buradan başlayabilirsin. Kavramlardan gerçek bir fonksiyonu satır satır çözmeye kadar — her adımı tlk-hex üzerinde deneyerek.

modül 01

Temeller başlangıç

Bir program yazarsın, derleyici onu makine koduna çevirir: işlemcinin doğrudan çalıştırdığı baytlar. Kaynak kod çoğu zaman elimizde olmaz — elimizde sadece o baytlar vardır. Tersine mühendislik, bu baytlardan geriye doğru giderek programın ne yaptığını anlamaktır.

Disassembler ne yapar?

İşlemci baytları okur ve komut olarak çalıştırır. Örneğin 48 83 EC 28 baytları aslında "yığından 0x28 bayt yer aç" demektir. Bir disassembler bu baytları insanın okuyabileceği assembly'ye çevirir:

bayt → assembly
48 83 EC 28      sub     rsp, 28h    ; yığında 40 bayt yer aç

tlk-hex bütün dosyayı bu şekilde çözer, sonra fonksiyonları, çağrıları ve veriyi birbirine bağlar.

Birkaç temel kavram

  • Register: İşlemcinin içindeki küçük, çok hızlı değişkenler. x64'te rax, rbx, rcx… diye adlandırılır.
  • Bellek adresi: Her baytın bir numarası vardır. 0x140001008 gibi onaltılık yazılır.
  • Komut (instruction): İşlemcinin tek adımı — mov, add, call…
  • Fonksiyon: Birlikte iş yapan komut dizisi; bir yerden call ile çağrılır, ret ile döner.
Neden onaltılık (hex)? Baytlar 0–255 arası değer alır ve 00–FF aralığı tam iki hex hanesine sığar. Bu yüzden adresler ve değerler hep hex yazılır. tlk-hex'te H ile bir sayıyı ondalığa çevirebilirsin.
modül 02

Dosya yapısı başlangıç

Bir .exe yalnızca koddan ibaret değildir. Windows'un onu belleğe doğru yükleyebilmesi için bir düzeni vardır: PE (Portable Executable) formatı. Linux tarafında bunun karşılığı ELF'tir.

Bölümler (sections)

Dosya, işlevine göre bölümlere ayrılır. tlk-hex'te Shift+F7 ile hepsini görürsün:

Bölümİçinde ne var
.textÇalıştırılabilir kod. Fonksiyonlar burada.
.rdataSalt-okunur veri: string'ler, import tablosu, sabitler.
.dataYazılabilir global değişkenler.
.rsrcKaynaklar: ikonlar, sürüm bilgisi, diyaloglar.
.pdatax64 istisna/çözülme bilgisi — fonksiyon sınırlarını bulmaya yarar.

Önemli noktalar

  • Giriş noktası (entry point): Program çalışınca ilk çalışan komut. tlk-hex'te Ctrl+E.
  • Import'lar: Programın başka DLL'lerden ödünç aldığı fonksiyonlar (CreateFileW, send…). Ne yaptığına dair en güçlü ipucu.
  • Export'lar: Bir DLL'in dışarıya açtığı fonksiyonlar.
  • Image base: Dosyanın belleğe yükleneceği başlangıç adresi (çoğu x64 EXE'de 0x140000000).
RVA vs offset: Dosyadaki konum (file offset) ile bellekteki adres (RVA + image base) farklıdır. tlk-hex ikisini de gösterir ve çevirmeyi senin yerine yapar — alt çubukta her ikisini de görürsün.
modül 03

Assembly okuma orta

Korkutucu görünür ama birkaç düzine komut işlerin %90'ını kaplar. Önce register'lar:

Register'lar

64-bit32-bitTipik kullanım
raxeaxDönüş değeri; genel hesap
rcx rdx r8 r9ecx edxFonksiyona geçen ilk 4 argüman (x64)
rbx rsi rdiebx esi ediKorunan genel değişkenler
rspespYığın işaretçisi (stack pointer)
rbpebpYığın çerçevesi tabanı
ripeipSıradaki komutun adresi

Aynı register'ın küçük parçaları da kullanılır: rax → eax (32) → ax (16) → al (8 bit).

En sık komutlar

KomutAnlamı
mov a, ba = b — değer kopyala
lea a, [b]a = b'nin adresi — adres hesapla (hep bellek okumaz)
add / subTopla / çıkar
xor a, aRegister'ı sıfırla (kendisiyle XOR = 0)
cmp a, bKarşılaştır (a - b) ve bayrakları ayarla
test a, aa sıfır mı diye bakar (çok yaygın)
jmpKoşulsuz atla
jz / jeEşitse / sıfırsa atla  ·  jnz/jne: değilse
callFonksiyon çağır
retFonksiyondan dön
push / popYığına koy / al

Çağrı kuralı (x64 Windows)

Bir fonksiyon çağrılırken argümanlar sırayla rcx, rdx, r8, r9 register'larına konur; fazlası yığına gider. Dönüş değeri rax'tedir. Yani şunu gördüğünde:

.text — bir çağrı
mov     rcx, 1
lea     rdx, aMerhaba      ; "merhaba"
call    MesajYaz           ; MesajYaz(1, "merhaba")
mov     ebx, eax           ; dönüş değerini sakla

…aslında MesajYaz(1, "merhaba") çağrısını okuyorsun demektir.

Yığın değişkenleri

Yerel değişkenler yığında tutulur. tlk-hex bunları var_18 (yerel) ve arg_8 (argüman) gibi otomatik adlandırır; [rsp+88h+var_18] yazısı "bu fonksiyonun bir yerel değişkeni" demektir. Üzerine gel, N ile anlamlı bir isim verebilirsin.

modül 04

İlk analiz orta

Artık gerçek bir fonksiyon okuyalım. Ana sayfadaki örneği alalım ve satır satır çözelim:

IDA View-A · ParseConfigFile
140001008 ParseConfigFile proc near
140001008   mov     r11, rsp
14000100B   sub     rsp, 88h              ; yığında yer aç
140001012   mov     rax, cs:__security_cookie  ; yığın koruması
140001019   xor     rax, rsp
14000101C   mov     [rsp+88h+var_18], rax
140001053   lea     rcx, aConfigLoaded    ; "config loaded: %s"
14000105A   mov     edx, esi
140001073   call    LogMessage            ; LogMessage("config loaded: %s", esi)
140001080   call    cs:__security_check_cookie
140001085   add     rsp, 88h              ; yeri geri ver
14000108C   retn

Ne okuduk?

  • Baş kısım (sub rsp + security_cookie): Her derleyici fonksiyonunun standart açılışı. Yığın taşmasına karşı koruma. Mantığı burada değil.
  • Orta kısım: lea rcx, aConfigLoaded bir metnin adresini alıyor, call LogMessage onu yazdırıyor. Demek ki bu fonksiyon bir şeyi logluyor.
  • Son kısım (security_check + add rsp + retn): Standart kapanış.

Yani gürültüyü eleyince fonksiyonun özü tek satır: "config loaded" mesajını logla. Gerçek analizin yarısı, bu standart kalıpları tanıyıp önemli olana odaklanmaktır.

tlk-hex ipucu: Bir string'in nerede kullanıldığını merak ediyorsan üzerine gel, X'e bas — onu kullanan tüm yerler listelenir. Tersi de geçerli: bir call hedefine çift tıkla, o fonksiyonun içine girersin.

Nereden başlamalı?

  • String'ler (Shift+F12): "invalid password", "connecting to..." gibi metinler programın niyetini ele verir. İlginç bir string'e X ile onu kullanan koda git.
  • Import'lar: socket/connect görürsen ağ var; CryptEncrypt görürsen şifreleme var.
  • Giriş noktası (Ctrl+E): programın en tepesinden akışı izle.
  • Bulgular sekmesi: tlk-hex şüpheli/ilginç noktaları zaten özetler — hızlı bir harita verir.
modül 05

Akışı çıkar orta

Space ile grafiğe geç. Bloklar arası oklar programın mantığıdır. Birkaç kalıbı tanı:

if / else

Bir cmp + koşullu atlama, bir dallanmadır. Grafikte iki ok çıkar: yeşil = koşul doğru, kırmızı = yanlış.

bir karar
cmp     eax, 0
jz      loc_hata       ; eax == 0 ise hataya atla
; ... buraya düşerse eax != 0 (başarı yolu)

C diliyle: if (eax == 0) goto hata;

döngü (loop)

Grafikte yukarı doğru dönen bir ok gördüğünde orası bir döngüdür: blok, kendinden önceki bir bloğa geri atlıyordur. Genelde bir sayaç (inc/dec) ve bir cmp ile biter.

switch

Bir değere göre çok yöne dallanma. tlk-hex atlama tablolarını otomatik çözer ve jumptable ... case 3 gibi yorumlar ekler — hangi değerin nereye gittiğini doğrudan okursun.

Kestirme: Blok block okumak yerine F5'e bas. tlk-hex akışı senin için if/goto'lu C benzeri bir özete çevirir. Kesin değildir ama mantığı hızlı kavratır.
modül 06

Pratik ileri

Okumak öğretmez; çözmek öğretir. Başlamak için güvenli ve meşru yollar:

  • Kendi programını derle, sonra analiz et. C/C++ ile küçük bir program yaz, tlk-hex'te aç. Kaynağı bildiğin için assembly karşılığını birebir görürsün — en hızlı öğrenme yöntemi budur.
  • CTF (Capture The Flag): Güvenlik yarışmalarının "reversing" kategorileri tam bu iş için tasarlanmıştır; çözmen için yapılmış dosyalardır.
  • Crackme'ler: Yazarının bilerek "beni çöz" diye paylaştığı alıştırma programları. Burada amaç bir mantığı anlamaktır — başkasının ticari yazılımını kırmak değil.
  • Zararlı yazılım analizi: İleri seviye. Örnekleri izole bir ortamda, yalnızca statik olarak incele (tlk-hex çalıştırmaz, bu yüzden okuma güvenlidir).
Sınır nerede? Tersine mühendislik; öğrenmek, kendi kodunu incelemek, güvenlik araştırması ve izinli testler için meşrudur. Satın almadığın bir yazılımın lisans/kopya korumasını kaldırmak (crack) ya da başkasının hakkını ihlal etmek bunun dışındadır. Yetkin olduğun dosyalarda çalış.

Bir çalışma düzeni

  • Önce genel bak: string'ler, import'lar, bulgular. Program kabaca ne yapıyor?
  • İlginç bir ipucundan içeri gir (X ile string/import'tan koda).
  • Fonksiyonu grafikte oku, standart kalıpları ele; F5 ile doğrula.
  • İsim ver (N) ve not düş (;). Anladıkça dosya okunur hale gelir.
  • Ctrl+W ile kaydet; yarın kaldığın yerden devam et.
modül 07

Baştan sona bir EXE'yi çözmek orta

Buraya kadar parçaları ayrı ayrı gördük: string'ler, import'lar, xref'ler, assembly, graph ve pseudocode. Şimdi hepsini tek bir gerçek analiz akışında birleştireceğiz. Acele etme — bu modülün amacı hız değil, bir binary'ye nasıl soru sorulacağını kavramak.

Elimizde kaynak kod yokmuş gibi davranacağız ve şu sorulara yalnızca dosyanın kendisinden cevap arayacağız:

  • Program hangi girdiyle ilgileniyor?
  • Kararı hangi fonksiyon veriyor?
  • Hangi koşul hangi sonuca gidiyor?
  • Fonksiyonlar birbirine nasıl bağlanıyor?
  • Ve en önemlisi: bulduğumuzun doğru olduğunu nasıl kanıtlarız?
Bu alıştırmada kendi derlediğimiz küçük bir EXE kullanıyoruz. Böylece analiz tamamen güvenli, yasal ve doğrulanabilir: sonunda bulduğumuzu gerçek kaynakla karşılaştırabiliriz.

1 · Hedef programı oluştur

Önce analiz edeceğimiz küçük programı yazıyoruz. Bir sample.c dosyası aç:

sample.c
#include <stdio.h>
#include <string.h>

static int classify(const char *mode)
{
    if (strcmp(mode, "debug") == 0)
        return 2;
    if (strcmp(mode, "safe") == 0)
        return 1;
    return 0;
}

int main(int argc, char **argv)
{
    const char *mode = argc > 1 ? argv[1] : "safe";
    int result = classify(mode);
    printf("mode=%s result=%d\n", mode, result);
    return result;
}

Programın ne yaptığını şimdilik kafandan sil. Kaynağı yalnızca hedefi üretmek için kullanıyoruz; EXE hazır olduktan sonra ona sadece binary tarafından bakacağız. Derlemek için (hangisi eldeyse):

MSVC (Developer Command Prompt)
cl /O2 /Fe:sample.exe sample.c
MinGW-w64
gcc -O2 -o sample.exe sample.c

Artık elimizde sample.exe var. Bundan sonra kaynağı kapat.

2 · Aç ve önce haritayı çıkar

sample.exe'i tlk-hex penceresine sürükle. Otomatik analiz başlar ve bölümleri, fonksiyonları, çağrıları, string'leri, xref'leri ve import'ları çıkarır.

Analiz bitince hemen assembly okumaya dalma. Önce dosyanın genel havasını al. Tersine mühendislikte ilk hedef ayrıntı değil, bağlamdır — nerede olduğunu bilmeden tek tek komut okumak seni yorar.

3 · Önce string'lere bak

Shift+F12 ile Strings'i aç. Şuna benzer metinler ararsın:

Strings
debug
safe
mode=%s result=%d

Bu üç satır, daha tek bir assembly komutu okumadan bize çok şey anlatıyor. debug ve safe büyük ihtimalle kullanıcı girdisiyle karşılaştırılan değerler; mode=%s result=%d ise programın sonunda bir sonuç yazdırdığını düşündürüyor.

Ama bunlar şimdilik yalnızca hipotez. Tersine mühendisliğin altın kuralı: bir şey mantıklı görünüyor diye doğru sayma; xref ve kontrol akışıyla doğrula.

4 · String'den koda git (xref)

debug string'ini seç ve X'e bas. Bu, string'in nerede kullanıldığını (cross-reference) gösterir. Bir kod referansına git. Aynısını safe için de yap.

İki string'in referanslarının aynı fonksiyona ya da birbirine çok yakın kod alanlarına gittiğini görürsün. Bu güçlü bir ipucu:

Bu fonksiyon büyük olasılıkla gelen değeri "debug" ve "safe" ile karşılaştırıyor.

5 · Import'lara bak, kanıtları birleştir

Şimdi programın dışarıdan kullandığı fonksiyonlara bak. Derleyiciye göre isimler biraz değişse de şunları görürsün:

ImportNe yapar
strcmpİki C string'ini karşılaştırır (eşitse 0 döner)
printfBiçimlendirilmiş metni ekrana yazar

Artık üç bağımsız kanıt birikti: "debug", "safe" ve strcmp. Üçü birlikte güçlü bir ihtimale işaret ediyor: program bir string'i "debug" ve "safe" ile karşılaştırıyor. Yine de assembly'de görmeden emin olmayacağız.

6 · Kararı veren fonksiyonu oku

debug xref'inden ulaştığın fonksiyonun başına git. Otomatik adı sub_140001000 gibi olabilir — gerçek ismini henüz bilmiyoruz. İçeride şuna benzer bir kalıp görürsün (tam komutlar derleyiciye göre değişir, önemli olan kalıbı okumak):

karşılaştırma kalıbı
lea     rdx, aDebug        ; ikinci argüman: "debug"
mov     rcx, rbx          ; ilk argüman: gelen girdi
call    strcmp            ; strcmp(girdi, "debug")
test    eax, eax          ; dönüş 0 mı? (eşit mi?)
jz      loc_debug         ; eşitse debug bloğuna atla

İlk iki satır strcmp'nin argümanlarını hazırlar. strcmp iki string aynıysa 0 döndürür; test eax, eax sonucun sıfır olup olmadığına bakar ve jz (sıfırsa atla) ilgili bloğa gider. Yani buradaki mantık net:

C karşılığı
if (strcmp(input, "debug") == 0)

7 · Fonksiyona anlamlı isim ver

Ne yaptığını artık yaklaşık biliyoruz. Fonksiyonun üstünde N'ye basıp sub_140001000 yerine classify_mode yaz.

İsimlendirme lüks değil, yöntemin kalbidir. Başta ekranda onlarca sub_140001000 görürsün; bunları classify_mode, print_result, load_config gibi adlara çevirdikçe dosya her geçişte biraz daha okunur hale gelir.

8 · Graph'a geç, akışı gör

Fonksiyonun içindeyken Space'e bas; metin yerine akış grafiğini görürsün. Bu örnekte kabaca şöyle bir karar ağacı bekliyoruz:

beklenen karar yapısı
                girdi
                  │
                  ▼
          girdi == "debug"?
           ┌──────┴──────┐
         evet           hayır
           │              │
           ▼              ▼
       return 2    girdi == "safe"?
                    ┌─────┴─────┐
                  evet         hayır
                    │            │
                    ▼            ▼
                return 1     return 0

Assembly'de karmaşık görünen şey, grafikte bir bakışta anlaşılır. İlk karşılaştırmanın hayır (false) yolunu takip edince, bir süre sonra "safe" string'inin kullanıldığı ikinci bir karşılaştırma görürsün — mantığı birebir aynı:

ikinci karşılaştırma
lea     rdx, aSafe
mov     rcx, rbx
call    strcmp
test    eax, eax
jz      loc_safe          ; if (strcmp(input, "safe") == 0)

9 · Return değerlerini takip et

Şimdi her yolun sonunda hangi değerin döndüğüne bak. x64 Windows'ta tamsayı dönüş değeri eax register'ından döner:

dönüş değerleri
mov     eax, 2
ret                     ; return 2;  (debug yolu)

mov     eax, 1
ret                     ; return 1;  (safe yolu)

xor     eax, eax
ret                     ; return 0;  (xor = sıfırla; varsayılan yol)

xor eax, eax register'ı sıfırlar — yani return 0; demek. Bu küçük kalıbı tanımak çok işine yarayacak, her yerde görürsün.

10 · Mantığı yeniden kur ve F5 ile karşılaştır

Topladığımız kanıtları birleştirince binary bize şunu söylüyor:

çıkardığımız mantık
int classify_mode(const char *input)
{
    if (strcmp(input, "debug") == 0)
        return 2;
    if (strcmp(input, "safe") == 0)
        return 1;
    return 0;
}

Kaynak elimizde olmadan fonksiyonun davranışını çıkardık — tersine mühendisliğin asıl amacı da bu: kaynağı birebir geri almak değil, davranışı yeterince doğru anlamak.

Şimdi F5'e bas; tlk-hex aynı fonksiyonu C benzeri pseudocode'a çevirir ve senin elle çıkardığın mantığa çok yakın bir şey gösterir. Hızlıdır ama bir uyarı:

Decompiler çıktısı gerçek kaynak kod değildir. Derleyici optimizasyonları, kaybolan tip bilgisi ve register yeniden kullanımı yüzünden yanlış ya da eksik olabilir. Önemli sonuçları her zaman assembly ve graph'tan doğrula. İdeal sıra: pseudocode ile fikri edin → graph ile akışı kontrol et → assembly ile kanıtla.

11 · Nereden çağrılıyor + çıktı zinciri

classify_mode'un üstünde yine X'e bas — bu sefer fonksiyonun nereden çağrıldığını görürsün. Muhtemelen main'e ya da ona yakın bir yere çıkarsın:

çağrı yeri
mov     rcx, rbx          ; girdi
call    classify_mode
mov     esi, eax          ; dönüş değerini sakla (sonra kullanılacak)

Sonra tekrar Strings'e git, mode=%s result=%d string'ini X ile takip et; yakınında bir printf çağrısı görürsün. Böylece programın bütün zincirini çıkardık:

programın akışı
komut satırı girdisi  →  classify_mode  →  0 / 1 / 2  →  printf  →  çıkış

12 · İsimlendir, yorumla, kaydet

Bulduklarını kalıcı hale getir:

  • N ile isimlendir: sub_140001000 → classify_mode, aDebug → mode_debug, aSafe → mode_safe…
  • ; ile önemli satırlara yorum düş: "gelen mode'u 'debug' ile karşılaştırır" ya da "varsayılan yol 0 döner".
  • Ctrl+W ile kaydet. tlk-hex isim/yorum/çalışmayı ayrı tutar; orijinal EXE değişmez ve dosyayı tekrar açınca kaldığın yerden devam edersin.

Tersine mühendislik yalnızca binary okumak değil; bulduğunu düzenli biçimde kaydetme sürecidir. İyi isimlenmiş bir veritabanı, birkaç saat sonra ilk açtığın halinden tamamen farklı — çok daha okunur — görünür.

13 · Sonucu birkaç cümleyle yaz

İyi bir çalışmanın sonunda kısa bir sonuç yazabilmelisin. Bu örnek için:

analiz sonucu
sample.exe komut satırından bir "mode" değeri alıyor.
  mode == "debug"  →  sonuç = 2
  mode == "safe"   →  sonuç = 1
  diğer tüm değerler →  sonuç = 0
Sonuç, "mode=%s result=%d" formatıyla printf ile ekrana yazdırılıyor.

Kaynak koduna bakmadan programın temel davranışını çıkardık. İşte bütün mesele buydu.

Her binary'de kullanacağın çalışma sırası

Bu döngü neredeyse her dosyada işe yarar — ezberlemene gerek yok, birkaç kez yapınca kendiliğinden gelir:

metodoloji
 1. Dosyayı aç, otomatik analizi bekle
 2. Sections / imports / strings'e bak (bağlam)
 3. İlginç bir string veya import seç
 4. Xref (X) ile koda git
 5. Fonksiyonun graph'ını incele (Space)
 6. Call hedeflerini takip et
 7. Koşulları ve return değerlerini çıkar
 8. F5 pseudocode ile hızlı kontrol
 9. Assembly ile doğrula
10. Fonksiyonlara isim ver (N)
11. Yorum ekle (;)
12. Kaydet (Ctrl+W)
13. Sonucu birkaç cümleyle yaz

Bilinmeyen bir EXE'de ilk bakılacaklar

String'ler en güçlü ipuçlarıdır. Şunları görürsen dur ve string → X → fonksiyon zincirini izle:

dikkat çeken string'ler
error   failed   password   token   config
login   connect  http       registry file   debug

Import'lar programın hangi yetenekleri kullandığını ele verir:

Import grubuİşaret ettiği şey
CreateFileW · ReadFile · WriteFileDosya işlemleri
RegOpenKeyExW · RegSetValueExWRegistry kullanımı
connect · send · recvAğ haberleşmesi
CryptEncrypt · CryptDecryptŞifreleme
Tek bir import kesin sonuç vermez — kanıtları birlikte değerlendir. Bir fonksiyonu anlamadıysan iki soru sor: kimi çağırıyor? ve kim onu çağırıyor? Xref bu ikisinin de cevabıdır.

Statik analizin sınırı

tlk-hex şu an ağırlıklı olarak statik analiz yapar — programı çalıştırmadan inceler. Bu yüzden assembly analizi, fonksiyon keşfi, xref, string/import analizi, graph, pseudocode, PDB sembolleri, hex inceleme ve yama hazırlama rahatça yapılır. Ama bazı şeyler saf statikle her zaman tam görülemez:

dinamik analiz gereken durumlar
runtime'da hesaplanan değerler
bellekte açılan/çözülen şifreli kod
self-modifying (kendini değiştiren) kod
runtime unpacking (çalışırken açılan paketleyiciler)

Böyle durumlarda bir debugger gibi dinamik araçlar devreye girer. Yine de işin büyük kısmı statik başlar; ne arayacağını statik analizle bulup gerekirse dinamiğe geçersin.

Son görev — kendin dene

Örnek programı küçük bir değişiklikle yeniden derle (kaynağa tekrar bakmadan analiz etmeye çalış):

sample.c — yeni satır ekle
if (strcmp(mode, "admin") == 0)
    return 3;

Yeni EXE'yi tlk-hex'te aç ve şunları yalnızca binary üzerinden bul:

  • Yeni string hangisi?
  • Hangi fonksiyon kullanıyor?
  • Hangi koşuldan sonra çalışıyor?
  • Hangi değer dönüyor?
  • Graph'ta yeni dal (branch) nerede?
  • Pseudocode değişikliği doğru yakalıyor mu?

Bunların hepsini binary'den bulabiliyorsan ilk gerçek tersine mühendislik akışını tamamladın.

Kısa kural: Tahmin et → kanıt bul → isimlendir → doğrula → not al.
Bir binary ilk açıldığında anlamsız adreslerden oluşan dev bir dosya gibidir. Çözdüğün her string, xref ve fonksiyon bu yapının küçük bir parçasını anlamlı kılar. Amaç bütün assembly'yi ezberlemek değil; programın davranışını adım adım yeniden kurmaktır.
modül 08

Yama: bir programın davranışını değiştirmek orta

Bazen doğru parolayı/serial'i bulmak istemezsin — programın davranışını değiştirmek istersin. Örneğin bir kontrolü tersine çevirip "her zaman geçer" hale getirmek. Buna yama (patching) denir: diskteki baytları düzenlersin. tlk-hex bunu güvenli yapar — orijinal dosyaya dokunmaz, değişiklikleri ayrı tutar.

Bu beceri; kendi yazılımın, CTF, crackme ve izinli analiz içindir. Satın almadığın bir yazılımın korumasını kaldırmak için değil.

Ne zaman yama?

  • Kontrolü sağlamak (keygen) zor, ama tek bir dalı değiştirmek kolay olduğunda.
  • Bir özelliği/kontrolü hızlıca devre dışı bırakıp aşağısını incelemek istediğinde.
  • Bir hipotezi test etmek için bir değeri/akışı geçici değiştirmek istediğinde.

Koşullu atlamayı bul

Çoğu kontrol bir cmp/test + koşullu atlama ile biter. Tipik bir "başarısızsa reddet" kalıbı:

karar
cmp     eax, 0DEADBEEFh
jnz     loc_denied        ; eşit DEĞİLSE reddet bloğuna atla
; ... buraya düşerse "granted" (başarılı) yol

Burada akışı değiştirmenin birkaç yolu var — hepsi tek bir baytı değiştirmekle olur:

İstediğinNasılBayt
Koşulu tersine çevirjnz → jz (ya da tam tersi)75 ↔ 74
Atlamayı hiç yapmaKoşullu atlamayı NOP'la90 90
Her zaman atlaKoşulluyu koşulsuz yap: jnz → jmp75 → EB
74 = jz/je, 75 = jnz/jne, 90 = nop, EB = kısa jmp. Bu dört baytı ezberlemek işini çok hızlandırır.

Hex View ile yamala

  1. Değiştireceğin komutun üstüne gel (örn. jnz loc_denied).
  2. Hex View sekmesine geç; imleç o baytta durur. F2 ile düzenleme moduna gir.
  3. Baytı yaz: 75'i 74 yap (ya da ne istiyorsan). F2 / Esc ile çık.
  4. IDA View'a dön; komut artık jz olarak görünür — mantık tersine döndü.

Diske uygula (orijinal bozulmadan)

Yama şimdilik sadece veritabanında. Kalıcı bir dosya üretmek için:

  • Dosya → Yamaları dosyaya uygula… — yamalı baytlarla yeni bir exe yazar (orijinali ayrı kalır).
  • Dosya → DIF dosyası oluştur… — değişen baytları metin olarak dışa aktarır (paylaşmak/belgelemek için).
  • Dosya → Yamalı baytları listele — ne değiştirdiğini tek listede gösterir.
Yama davranışı değiştirir: önce bir yedek al, sonucu çalıştırıp doğrula, ve yalnızca yetkili olduğun dosyalarda yap.

Alıştırma

Pratik crackme'lerden ikisi tam bu modül için:

  • crackme05 — doğru anahtarı bulmak zor (hedef 0DEADBEEFh). cmp'nin ardındaki dalı bulup yamala → "Access granted".
  • crackme06 — parola bir stack string (Strings'te yok); bayt yazmalarını okuyarak çıkar.

İkisini de crackmes sürümünden indir. Diğer seviyeler ve çözüm ipuçları Modül 07'deki akışla birleşir.

modül 09

Paketleyiciler & triyaj: kod gizliyse orta

Dosyayı açtın; disassembly çok küçük, import tablosunda beş girdi var ve stringler anlamsız mı? Büyük ihtimalle paketlenmiş ya da korumalı bir dosyaya bakıyorsun: gerçek kod sıkıştırılmış/şifrelenmiş ve ancak çalışma anında bellekte açılıyor. Statik olarak okumadan önce bunu fark etmen gerekir — ve tlk-hex Bulgular panelinde sana dört hızlı işaret verir.

1. Entropi — bir bölüm ne kadar rastgele?

Entropi, rastgeleliği 0–8 ölçeğinde (bayt başına bit) ölçer. Normal kod 6–6.8 civarındadır. Sıkıştırılmış veya şifreli veri 7.5–8'e doğru çıkar, çünkü paketli baytlar gürültü gibi görünür. tlk-hex Segments görünümünde her bölüm için entropi değeri gösterir ve kaynak olmayan bir bölüm 7.5 üstüne çıkınca bulgu üretir.

EntropiGenelde şu demektir
< 6.0Metin, tablolar, sıkıştırılmamış veri
6.0 – 7.0Sıradan makine kodu
> 7.5Sıkıştırılmış / şifreli — muhtemelen paketli
Tek başına yüksek entropili bir .rsrc normaldir (PNG ya da zip tutabilir). Asıl uyarı, kod bölümündeki yüksek entropidir.

2. Paketleyici imzaları — bölüm adları ele verir

Birçok paketleyici parmak izini bölüm adlarında bırakır. tlk-hex gömülü bir listeyle eşleştirir ve aracı senin için adlandırır:

BölümPaketleyici
UPX0 / UPX1UPX (ücretsiz, kolay açılır)
.aspack / .adataASPack
.vmp0 / .vmp1VMProtect (sanallaştırma)
.themida / winliceThemida / WinLicense
.petite, .mpress, .fsgPetite / MPRESS / FSG

UPX en kolay olanı: upx -d ornek.exe onu açar, sonucu normal şekilde analiz edersin. VMProtect/Themida ise bambaşka bir lig — kodu sanallaştırır ve yalnızca-statik çalışmanın kapsamı dışındadır.

3. Seyrek import — yetenek eksik

Gerçek bir arayüz programı onlarca–yüzlerce fonksiyon import eder. DLL olmayan bir dosya yalnızca birkaç tane import ediyorsa ve ayrıca LoadLibrary + GetProcAddress görüyorsan, program gerçek API'sini import tablosundan gizlemek için çalışma anında çözüyordur. tlk-hex hem seyrek import tablosunu hem de dinamik çözüm desenini işaretler.

4. imphash — import tablosunun parmak izi

imphash, import tablosu üzerinde alınan bir MD5'tir (her dll.fonksiyon, sırasıyla). Aynı kaynaktan/araçtan derlenen iki örnek, baytları farklı olsa bile genelde aynı imphash'i paylaşır — yani bir zararlı yazılım ailesini kümelemek ya da kardeş örnekleri bulmak için ucuz bir yoldur. tlk-hex bunu hesaplar ve Bulgular'da gösterir; ilgili örnekleri bulmak için bir tehdit-istihbaratı aramasına yapıştırabilirsin.

Geriye kalanı okumak

Paketli bir dosya bile biraz sızdırır. Açmadan önce şunlara göz at:

  • Mevcut birkaç import — VirtualAlloc + VirtualProtect klasik "bellek ayır, içine aç, çalıştırılabilir yap" üçlüsüdür.
  • Giriş noktası — paketleyici stub'ı kısadır ve yeni yazılmış belleğe bir jmp ile biter (orijinal giriş noktasına / OEP'ye "tail jump").
  • API çağrı yorumları — tlk-hex bilinen çağrıları parametreleriyle açıklar (ör. VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect)) ki açma stub'ı net okunsun.

Alıştırma

Sahibi olduğun zararsız bir programı al, bir kopyasını UPX ile paketle ve ikisini de tlk-hex'te aç:

  1. Segments entropisini karşılaştır — paketli kopyada kod bölümünün 7.5 üstüne çıkışını izle.
  2. UPX paketleyici bulgusunu ve seyrek import tablosunu doğrula.
  3. upx -d çalıştır, yeniden aç ve import'ların ile stringlerin geri geldiğini gör.
Yalnızca sahibi olduğun ya da analiz etmeye yetkili olduğun yazılımı paketle/aç. Satın almadığın yazılımın korumasını kırmaya asla çalışma.
başvuru

Sözlük

Disassembler
Makine kodu baytlarını okunur assembly'ye çeviren araç. tlk-hex bir disassembler'dır.
Decompiler
Assembly'yi daha üst seviye (C benzeri) koda çevirmeye çalışan araç. tlk-hex'in F5'i basit bir decompiler gibidir.
Assembly
İşlemci komutlarının insan okuyabilir hali — mov, call, jmp…
Register
İşlemci içindeki çok hızlı küçük depolar: rax, rcx…
Opcode
Bir komutu temsil eden ham bayt(lar). C3 = ret gibi.
RVA
Relative Virtual Address — image base'e göre göreli bellek adresi.
Entry point
Program çalışınca ilk çalışan komutun adresi.
Import
Programın başka bir DLL'den kullandığı dış fonksiyon.
Xref (cross-reference)
Bir adrese/isme yapılan referanslar — "burası nereden çağrılıyor/kullanılıyor?"
Stack (yığın)
Fonksiyonların yerel değişken ve dönüş adresi için kullandığı LIFO bellek alanı.
Calling convention
Argümanların hangi register/yığın sırasıyla geçtiğini belirleyen kural.
Prologue / Epilogue
Fonksiyonun standart açılış/kapanış kodu (yığın ayarı, koruma).
Basic block
Dallanmasız, baştan sona akan komut dizisi; grafiğin bir kutusu.
PDB / Sembol
Fonksiyon isimlerini içeren yardımcı dosya. Yüklenince sub_X gerçek adına döner.
Patch (yama)
Dosyadaki baytları değiştirmek. tlk-hex'te F2 ile; orijinal bozulmadan.
Packer
Kodu sıkıştırıp/şifreleyip çalışınca açan koruma. Yüksek entropi olarak görünür.
Entropi
0–8 arası rastgelelik ölçüsü. Kod bölümünde > 7.5 genelde paketli/şifreli demektir.
imphash
Import tablosunun MD5 parmak izi; aynı araç/aileden örnekler arasında paylaşılır.