Bilindiği gibi yazılan programlar debug
programları ile işleyişleri incelenip farklı işlere
zorlanabilirler. Diğer taraftan sourcer türü programlar
aracılığıyla yazılan programların kaynak kodları
çıkartılabilir. Burada bu türlü işlemlerin nasıl
engelleneceği anlatılmaktadır.
Anti-Debugging metodları iki katagoride
incelenebilir:
1. Koruma Amaçlı;
2. Kendi Kendini Değiştiren Kodlar.
Bu tür koruma metodları bugünlerde en fazla
virüslerde kullanılmaktadır. Bu tür trikler virüsün
yaptığı işin çözülmesini engeller böylece virüsün
nasıl bulaştığı ve orjinal kodu çözülemez. Ayrıca kaynak
kodu da çıkartılamaz. Bu tür örneklere ait kaynak kodlar
dökümanın sonuna doğru verilmiştir.
Bu tür triklerin en fazla kullanım alanı
bulduğu başka bir alan ise kopyalamaya karşı koruma
programlarıdır. Bu tür programlar içeride nasıl kontrol
yaptıkları tespit edilemesin diye Anti-Debug, Anti-Trace ve
Anti-Source metodlarıyla korunmaktadırlar. Dolayısı ile
kırılmaları oldukça güçleşmektedir.
1. Koruma Amaçlı:
Koruma amaçlı metodlar kullanıcının kodu
debug etmesi veya kaynak kodunu çıkartmasını engelemek için
kullanılır. Bu tür metodlar normal çalışma esnasında
düzgün çalışmasına rağmen Debug yapılırken sistemi
sakatlamakta veya Debug programını göçertmektedirler.
1.1. Interrupt”ların devre dışı
bırakılması:
Interrupt”ların devre dışı bırakılması
en çok kullanılan metodlardan biridir. Değişik metodları
vardır.
1.1.1. Donanım kontrolü ile interrupt”ın
devre dışı bırakılması:
Port 21h üzerinden kontrol edilen 8259
Interrupt kontrolcüsüne komut göndererek IRQ hatlarının
devre dışı bırakılması esasına dayanır. Port 21h
adresindeki her bit bir IRQ hattını kontrol eder. Bit 0 IRQ
0″ı, Bit 1 IRQ 1″i şeklinde. Buradan 0-7 arası IRQ”lar kontrol
edilebilir. Bu arada IRQ 1 klavye kontrolcüsünden gelen sinyali
taşır. Klavyeden her tuşa basılışta IRQ1 çağrılır. Bu
IRQ”nun karşılığı olan INTERRUPT CPU tarafından
çağrılarak basılan tuşla ilgili bilginin klavyenin
belleğinden alınması sağlanır. Böylece her tuşa
bastığınızda bilgi uçmaz. Belleğe alınır. Eğer Debug
işlemi esnasında bir sonraki komuta geç komutu verilemezse
program debug edilemez. Basitçe klavyeyi kilitleyerek
kullanıcının bir sonraki tuşa basması engellenir. Böylece
program korunmuş olur.
Örnek:
Adres
Hex
Instruction
| CS:0100 | E4 21 | IN AL,21 |
| CS:0102 | 0C 02 | OR AL,02 |
| CS:0104 | E6 21 | OUT 21,AL |
Diğer taraftan, Klavyeye ait PPI (Programmable
Peripheral Interface)”ın bulunduğu Port 61h”a komut
gönderilerekte klavye kilitlenebilir.
Örnek:
Adres
Hex
Instruction
| CS:0100 | E4 61 | IN AL,61 |
| CS:0102 | 0C 80 | OR AL,80 |
| CS:0104 | E6 61 | OUT 61,AL |
1.1.2. Yazılımla Interrupt”ın devre dışı
bırakılması:
Bu biraz daha kolay bir anti-debugging
metodu”dur. Tüm yapmanız gereken Debugger”lar tarafından
kullanılabileceğini düşündüğünüz interrupt vektörlerini
farklı adreslere yöneltmek veya boşaltmak. Bir başka metod
ise hataları kontrol eden interrupt vektörlerini kendi
üzerinize alarak üstünüze aldığınız hata konrolcüsünün
hatasını devreye sokmak. Unutmamanız gereken işlem bittikten
sonra eski vektörleri yerine getirmenizdir. Interrupt
vektörünü Int 21h”ın 25h fonksiyonunu kullanmak yerine
kendinizin değiştirmesi.Çünki Int 21h”ı
çağırdığınızda debugger bunu anlayıp kendisini zincire
ekleyerek sizi kandırabilir. Bu sebeble gidip kendiniz
değiştirmeniz önerilir. Aşağıdaki örnek interrupt 03h -
Breakpoint interrupt”ının ele geçirilmesi gösterilmektedir.
Örnek:
Adres
Hex
Instruction
| CS:0100 | EB 04 | JMP 0106 |
| CS:0102 | 00 00 | ADD [BX+SI],AL |
| CS:0104 | 00 00 | ADD [BX+SI],AL |
| CS:0106 | 31 C0 | XOR AX,AX |
| CS:0108 | 8E C0 | MOV ES,AX |
| CS:010A | 26 8B 1E 0C 00 | MOV BX,ES:[000C] |
| CS:010F | 89 1E 02 01 | MOV [0102],BX |
| CS:0113 | 26 8B 1E 0E 00 | MOV BX,ES:[000E] |
| CS:0118 | 89 1E 04 01 | MOV [0104],BX |
| CS:011C | 26 C7 06 4C 00 00 00 |
MOV Word Ptr ES:[000C],0000 |
| CS:0123 | 26 C7 06 4E 00 00 00 |
MOV Word Ptr ES:[000E],0000 |
1.1.3. Vektör Ayarlamaları
Bu metod interrupt vektörleri üzerinde
oynayarak, bu vektörlere akışı yönlendirmeye dayanır. Bu
tür bir işlem kodçözücü kodda da kullanılmaktadır. (Bkz.
2.1) Bu metodda bilgiler interrupt vektörlerinin işaret ettiği
yerde tutulur. Elbetteki normal çalışma esnasında 01h, 03h
kullanılmaz. Tabi debug etmeyi düşünmüyorsanız. Bu metod
gayet güzel çalışmaktadır.
Örnek:
Adres
Hex
Instruction
| CS:0100 | 31 C0 | XOR AX,AX |
| CS:0102 | 8E D0 | MOV SS,AX |
| CS:0104 | BC 0E 00 | MOV SP,000E |
| CS:0107 | 2E 8B 0E 34 12 | MOV CX,CS:[1234] |
| CS:010C | 50 | PUSH AX |
| CS:010D | 31 C8 | XOR AX,CX |
| CS:010F | 21 C5 | AND BP,AX |
| CS:0111 | 58 | POP AX |
| CS:0112 | E2 F8 | LOOP 010C |
1.1.4. Interrupt Yerdeğiştirme
Bu biraz çatlakça bir trik. Ve sadece
programınızın artık daha fazla debug istemediğini
düşünüyorsanız kullanın. Interrupt 16h ve 21h”ın
vektörlerini 01h ve 03h”a kopyalamakla ne yapılabilir. Normal
bir çalışmada böyle bir şey yapılmaz. Eğer kullanıcı
programı debug etmek isterse programın içinde geçen INT 01
komutları normal bir INT komutuna döndürmek zorundadır. Bu
trik çok etkili olabilir. Çünki herhangi bir şekilde CD01(INT
01)”i CD16 (INT 16)”ya değiştirmek kolaydır. Ancak Int
03″için özel komut olan 0CCh tek byte”tır. ve iki byte”lık
bir komutla değiştirilemez.
Örnek:
Adres
Hex
Instruction
| CS:0100 | FA | CLI |
| CS:0101 | 31 C0 | XOR AX,AX |
| CS:0103 | 8E C0 | MOV ES,AX |
| CS:0105 | 26 A1 84 00 | MOV AX,ES:[0084] |
| CS:0109 | 26 A3 04 00 | MOV ES:[0004],AX |
| CS:010D | 26 A1 86 00 | MOV AX,ES:[0086] |
| CS:0111 | 26 A3 06 00 | MOV ES:[0006],AX |
| CS:0115 | B4 4C | MOV AH,4C |
| CS:0117 | CD 01 | INT 01 |
1.2. Zaman Kontrolü:
Nispeten daha az kullanılan bir metoddur.
Ancak programın çalışması esnasında tüm interrupt”ları
devre dışı bırakan debuggerları (Örnek: Borland Turbo
Debugger) devre dışı bırakmak için gayet kullanışlı bir
metoddur. Bu metod basitçe saati kontrol eder ve değişene
kadar kısır döngüde bekler. Bu değer interrupt 08h
tarafından değiştirilir. Diğer bir örnek eğer Port 21h”dan
okunan değer (IN) 01h ile OR”lanır ve tekrar Port 21h”a
yazılsa bile (IRQ 0″ı devreye sokar) Eğer hala devreye
girmemiş ise o zaman debugger aktiftir. Şunu dikkate almak
gerekiyor. Bu metod RUN yapılıyorsa etkindir. Eğer TRACE veya
STEP by STEP çalıştırılsa çalışmaz.
Örnek:
Adres
Hex
Instruction
| CS:0100 | 2B C0 | SUB AX,AX |
| CS:0102 | FB | STI |
| CS:0103 | 8E D8 | MOV DS,AX |
| CS:0105 | 8A 26 6C 04 | MOV AH,[046C] |
| CS:0109 | A0 6C 04 | MOV AL,[046C] |
| CS:010C | 3A C4 | CMP AL,AH |
| CS:010E | 74 F9 | JZ 0109 |
1.3. Debugger”ı yanıltmak:
Bu metod gayet güzel bir tekniktir. Turbo
Debugger ve benzeri debugger”lar için kullanılır. Bu işlem
bir komutun ortasına atlayarak yapılır. Aslında bir sonraki
komut aptal bir komuttur. ve içerisinde başka bir komutu
içerir. Siz aptal komutu atlayıp gerçek komuta
atladığınızda debugger”ı yanıltmış olursunuz. Ancak
normal step debugger kullanıyorsanız (Örnek Debug veya SymDeb)
bu işe yaramayacaktır. Çünki komutlarınız komut komut
çalıştırıldığından jump ettiği noktada sizi
bekleyecektir.
Örnek:
Adres
Hex
Instruction
| CS:0100 | E4 21 | IN AL,21 |
| CS:0102 | B0 FF | MOV AL,FF |
| CS:0104 | EB 02 | JMP 0108 |
| CS:0106 | C6 06 E6 21 00 | MOV Byte Ptr [21E6],00 |
| CS:010B | CD 20 | INT 20 |
Şuna dikkat edin :
Adres
Hex
Instruction
| CS:0108 | E6 21 | OUT 21,AL |
Dikkat:
Bu trik herhangi bir debugger”ın
çalışmasını etkilemez. Bu sadece kullanıcının başka bir
komutun çalıştırldığını düşünürken başka bir komut
çalıştırılır.
1.4. Check CPU Flags:
This is a nice trick, effective against almost
any real mode debugger. What you should do is simply set the
trace flag off somewhere in your program, and check for it later.
If it was turned on, a debugger runs in the background…
Örnek:
Adres
Hex
Instruction
| CS:0100 | 9C | PUSHF |
| CS:0101 | 58 | POP AX |
| CS:0102 | 25 FF FE | AND AX,FEFF |
| CS:0105 | 50 | PUSH AX |
| CS:0106 | 9D | POPF |
Programın ortasında :
Adres
Hex
Instruction
| CS:1523 | 9C | PUSHF |
| CS:1524 | 58 | POP AX |
| CS:1525 | 25 00 01 | AND AX,0100 |
| CS:1528 | 74 02 | JZ 152C |
| CS:152A | CD 20 | INT 20 |
1.5. Debugger”ın çalışmasını kesme:
Bu teknik debugger RUN modunda iken
çalışmasını keser. Yapacağınız şey programın
içerisinde rastgele yerlere INT 3 komutu yerleştirmek. RUN
modunda olduğunuz halde program hep bir yerlerde kesilir ve siz
sürekli RUN demek durumunda kalırsınız. Bu ise oldukça
sıkıcı bir durum. Eğer debugger dışında iseniz bu bir
çırpıda geçecektir.
Örnek:
Adres
Hex
Instruction
| CS:0100 | B9 64 02 | MOV CX,0264 |
| CS:0103 | BE 10 01 | MOV SI,0110 |
| CS:0106 | AC | LODSB |
| CS:0107 | CC | INT 3 |
| CS:0108 | 98 | CBW |
| CS:0109 | 01 C3 | ADD BX,AX |
| CS:010B | E2 F9 | LOOP 0106 |
1.6. Bilgisayarı Stack kullanarak çökertmek:
Bu trik debugger”ların kendi stack”larını
kullanmak yerine kullanıcı programının stack”ını
kullanması esasına dayanır. Eğer debug çalışıyorsa stack
değişecektir. Stack kod alanını gösterdiğinden komutlar
işletilirken aradaki stack bölgesindeki abuk sabuk komutlar
sistemi çökertir. Böylece debug yapılırken sistem çöker.
Eğer debug çalışmıyorsa bu alanlar değişmeyeceğinden
program normal çalışır ve bu alanı geçer.
Önemli not: Bu kodu çalıştırırken CLI
yapıp iş bittikten sonra STI yapmayı unutmayın. Eğer bu
komut aralığında herhangi bir interrupt çağrılırsa bu alan
değişeceğinden bilgisayar olmadık bir yerde çöker.
Örnek:
Adres
Hex
Instruction
| CS:0100 | 8C D0 | MOV AX,SS |
| CS:0102 | 89 E3 | MOV BX,SP |
| CS:0104 | 0E | PUSH CS |
| CS:0105 | 17 | POP SS |
| CS:0106 | BC 0B 01 | MOV SP,010B |
| CS:0109 | 90 | NOP |
| CS:010A | 90 | NOP |
| CS:010B | EB 02 | JMP 010F |
| CS:010D | 90 | NOP |
| CS:010E | 90 | NOP |
| CS:010F | 89 DC | MOV SP,BX |
| CS:0111 | 8E D0 | MOV SS,AX |
1.7. TD386″yı V8086 modundayken çökertmek:
Bu Turbo Debugger”ın V8086 modulü (TD386)”nü
çökertmek için güzel bir metoddur. Temeli sıfıra bölme
işlemidir. Sıfıra bölme işlemi sistemi çökertip programın
kesilmesine neden olur. Halbuki sıfıra bölme işlemi INT
00h”ı çağırır. Eğer siz INT 00h vektörünü bir sonraki
komuta getirirseniz böylece sistem çakılmadan yoluna devam
eder. Halbuki Turbo Debugger bunu farkettiği anda programı
keser. Normal çalışırken ise sorun çıkmaz.
Önemli not: Eski INT 00h vektörünü
kaydetmeniz gerekiyor. İşiniz bittiğinde bu vektörünü
düzeltin. Eğer bunu yapmazsanız sistem bir sonraki INT 00h
çağrısında çökecektir.
Örnek:
Adres
Hex
Instruction
| CS:0100 | 31 C0 | XOR AX,AX |
| CS:0102 | 8E D8 | MOV DS,AX |
| CS:0104 | C7 06 00 00 12 01 | MOV WORD PTR [0000],0112 |
| CS:010A | 8C 0E 02 00 | MOV [0002],CS |
| CS:010E | B4 00 | MOV AH,00 |
| CS:0110 | F6 F4 | DIV AH |
| CS:0112 | B8 00 4C | MOV AX,4C00 |
| CS:0115 | CD 21 | INT 21 |
1.8. Herhangi bir V8086 Prosesini çökertme:
TD386″yı göçertme metodlarından biri
Hatalı Komut işletmektir. Ne yazıkki, V8086 ortamında
çalışan başka bir program tarafından işletilen bir hatalı
komut ta sizin sisteminizi etkileyebilir. Metod sıfıra bölme
metodu ile aynıdır. Ancak bu metodda interrupt 0Dh (13)
kullanılır. İşletilen hatalı bir komut programda bir sonraki
satırdan devam etmenizi sağlar. Ancak Debugger üzerinden
çalıştırıyorsanız işletilen komutla debugger duracak ve
programı kesecektir. Böylece debug yapılması engellenmiş
olur.
Önemli not: Orjinal interrupt vektörlerini
eski değerlerine döndürmezseniz herhangi bir sakatlık
durumunda sistemin çakılmasına neden olur.
Örnek:
Adres
Hex
Instruction
| CS:0100 | 31 C0 | XOR AX,AX |
| CS:0102 | 8E D8 | MOV DS,AX |
| CS:0104 | C7 06 34 00 13 01 | MOV WORD PTR [0034],0113 |
| CS:010A | 8C 0E 36 00 | MOV [0036],CS |
| CS:010E | 83 3E FF FF 00 | CMP WORD PTR [FFFF],+00 |
| CS:0113 | B8 00 4C | MOV AX,4C00 |
| CS:0116 | CD 21 | INT 21 |
2. Kendi Kendini Değiştiren Kodlar:
2.1. Şifreleme / Çözme algoritmaları :
İlk katagori basit bir kod, bu kod şifrelidir
ve basit bir çözme rutini eklenmiştir. Bu arada eğer debugger
bir breakpoint koymuşsa buda arada kaynayacağından farklı bir
komuta dönüşecektir. Dolayısıyla sistemin çakılmasına
neden olur. Böylece debugging işlemi kesilir. Aşağıdaki
örnek Haifa virüsünden alınmıştır. Eğer siz CS:0110
adresine breakpoint koyacak olursanız, asla bu adrese
ulaşamazsınız. Sebebi işlemin sonucunda ne olacağı kimse
tarafından bilinemez.Dolayısıyla ne kod işletileceği
bilinemez.
Not: eğer trace işlemi için çok fazla
uğraşmak istemiyorsanız. çözme işlemini kodun sonundan
itibaren başlatın. Böylece enazından çok fazla uğraşmadan
debug işlemini gerçekleştirebilirsiniz.
Örnek:
Adres
Hex
Instruction
| CS:0100 | BB 71 09 | MOV BX,0971 |
| CS:0103 | BE 10 01 | MOV DI,0110 |
| CS:0106 | 91 | XCHG AX,CX |
| CS:0107 | 91 | XCHG AX,CX |
| CS:0108 | 2E 80 35 97 | XOR Byte Ptr CS:[DI],97 |
| CS:010C | 47 | INC DI |
| CS:010D | 4B | DEC BX |
| CS:010E | 75 F6 | JNZ 0106 |
| CS:0110 | 07 | POP ES |
| CS:0111 | 07 | POP ES |
2.2. Kendini-Değiştiren Kodlar
2.2.1. Basit Kendini Değiştirme:
Bu metod basit bir şifreleme metodlarına
dayanır. Bir komutu işletmeden evvel değiştirmeye dayanır.
Aşağıdaki örnekte Call işlemi yapıldıktan sonra Call”dan
sonra gelen komut değiştirilir. Eğer “P”/Debug veya F8/Turbo
Debugger ile işletirseniz programın bir anda sonlandığını
göreceksiniz. Trace yaparsanız CD 20 olan bir sonraki komut
trace işlemi esnasında CC 20″ye dönecektir. CC Int 03h”ı
çağırdığından debugger kontrolü ele alabilir. Oysa burası
CALL yapıldıktan sonra rutinin içerisinde başka bir kod ile
değiştirilir.
Örnek:
Adres
Hex
Instruction
| CS:0100 | E8 04 00 | CALL 0107 |
| CS:0103 | CD 20 | INT 20 |
| CS:0105 | CD 21 | INT 21 |
| CS:0107 | C7 06 03 01 B4 4C | MOV Word Ptr [0103],4CB4 |
| CS:010D | C3 | RET |
Dikkat:
Adres
Hex
Instruction
| CS:0103 | B4 4C | MOV AH,4C |
2.2.2. The Running Line (Kendi kendini
şifreleme):
Bu örnek Kendi kendini kontrol eden ve kendi
kendini değiştiren bir koddur. Bazen “The Running
Line” ismiyle anılıur. Bu Serge Pachkovsky tarafından
kodlanmıştır.Basit trik noktaları olan bir gösterimdir.
Ancak burada bahsedilen diğer tekniklerden farklı olarak, Bu
göreceli olarak vektör tablosundaki korumaları engeller. Bu
son derece basitleştirilmiş bir örnektir.
XOR AX, AX
MOV ES, AX
MOV WORD PTR ES:[4*1+0],OFFSET TRACER
MOV WORD PTR ES:[4*1+2],CS
MOV BP, SP
PUSHF
XOR BYTE PTR [BP-1], 1
POPF
MOV AX, 4C00H ; This will not be traced!
DB 3 DUP ( 98H )
DB C5H, 21H
TRACER:
PUSH BP
MOV BP, SP
MOV BP, WORD PTR [BP+2]
XOR BYTE PTR CS:[BP-1], 8
XOR BYTE PTR CS:[BP+0], 8
POP BP
IRET
2.2.3. Prefetch Instruction Queue (PIQ)
Ayarlamaları
Bu metod herhangi bir debugger”ı atlatabilir.
veya herhangi bir bir seferde bir işlem yapan debugger”ları
atlatabilir.PIQ CPU üzerinde bulunur. Komutları işletmeden
evvel belleğe çekilir. Böylece işletmek için tekrar çağrı
yapılmaz. Zaten CPU içerisinde mevcuttur. Eski CPU”larda bu 6
veya 4″tür. Yenilerde ise 25 kadardır. işlem basitçe şu
şekilde tanımlanabilir. Bir şekilde PIQ ile Bellekteki bilgi
farklılaştırılır. Kod bu şekilde yazılır. Eğer debugger
içerisinde ise adım adım program çalıştırıldığından
PIQ her defasında temizlenir. Dolayısıyla her zaman güncel
kalır. Oysa normal çalışma esnasında orjinal kalan ve
değişmeyen PIQ”dan dolayı eski kod işleme sokulur. Ve program
bir sonraki satırı işler geçer. Oysa farklılaşmanın
olmadığında program döner durur.
Örnek:
Adres
Hex
Instruction
| CS:0100 | B9 75 02 | MOV CX,0275 |
| CS:0103 | BE 90 01 | MOV SI,0190 |
| CS:0106 | 89 F7 | MOV DI,SI |
| CS:0108 | AC | LODSB |
| CS:0109 | C7 06 0F 01 24 06 | MOV Word Ptr [010F],0624 |
| CS:010F | 34 73 | XOR AL,73 |
| CS:0111 | AA | STOSB |
| CS:0112 | C7 06 0F 01 24 06 | MOV Word Ptr [010F],0624 |
| CS:0118 | E2 EE | LOOP 0108 |
Şuna Dikkat edin.
Adres
Hex
Instruction
| CS:010F | 24 06 | AND AL,06 |
===============================================================================
Açıklamalar:
Program örneklerinde CLI/STI çifti
kullanılmamıştır. Interrupt kontrolü veya üzerine alma
durumlarında bu komutların başta ve sonra yer alması
gereklidir. Bunun sebebi eğer siz interrupt vektörünü
değiştirirken interrupt çağrılırsa sistem çökecektir.