23 Temmuz 2013 Salı

Unrar Kütüphanesiyle Paralel Şifre Bulucu #1


Yazıyı, birkaç kütüphaneyi anlattığım upuzun bir yazı olarak yayınlamaktansa üç ayrı bölümde yayınlamaya karar verdim. Böylesinin hem benim yazmam açısından hem de okurun takip edebilmesi açısından daha kolay olacağını düşünüyorum. Yazıda başlıkta da yazdığım gibi unrar kütüphanesini kullanarak ve MPI (Message Passing Interface) kütüphanesiyle birleştirerek kendi yazdığım paralel bir şifre bulucuyu anlatacağım. Ancak sonraki yazıları ne zaman yayınların bunu iş yoğunluğu gösterecek.


Bölüm1: Unrar kütüphanesi

Unrar kütüphesinin iyi bir dökümantasyonu yok ancak ben internette bulduğum bir belgeyi kısmen Türkçe'ye çevireceğim. Merak edenler bağlantıyı kullanarak belgenin orjinaline ulaşabilirler.

www.rarlabs.com'da linux için rar programı kaynak koduyla birlikte dağıtılıyor. Kodu satır satır incelemedim ama .rar dosyaları kişisel kodlarda kullanmak için Windows altında unrar.dll yada linux altında unrar.so adında rar açıcı (unrar) kütüphanesi geliyor. Ben kendi kodumda da bu kütüphaneyi kullandım. Kütüphane fonksiyonları kısaca şöyle:

  • RAROpenArchive: Prototipi,
HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData)

şeklindedir. Değişkenlerin ve yapıların tanımları unrar paketindeki dll.hpp dosyasında ve yukarıda verdiğim linkte var. Yapıda iki tane önemli alan var. Birisi char *ArcName diğeri de UINT OpenMode. İlki açılacak arşivin adını ve yolunu tutuyor. Diğeri de hangi modda açılacağını. OpenMode için iki tane öntanımlı değer var. Birisi RAR_OM_LIST diğeri de RAR_OM_EXTRACT. Listeleme modu biraz daha hızlı ama adı üzerinde sadece listelemek için. Hata durumunda NULL handle döndürüyor. 

  • RARCloseArchive: Prototipi,
int PASCAL RARCloseArchive(HANDLE hArcData)

şeklindedir. RAROpenArchive'den alınan handle ile dosya kapatılır.

  • RARReadHeader: Prototipi,
int PASCAL RARReadHeader(HANDLE hArcData, struct RARHeaderData *HeaderData)

şeklindedir. Açılmış arşiv dosyanın içerisindeki ilk dosyanın başlığı okunur. Dosyalara ait başlık bilgileri önemlidir çünkü dosyanın şifrelenip şifrelenmediği bilgisi başlıktan alınır. Fonksiyon arşiv dosyası açıldıktan hemen sonra çağırılır. Eğer dosya adları şifrelenmişse fonksiyon ERAR_MISSING_PASSWORD hatasını döndürür. 

.rar dosyalarda iki tür şifreleme yapılabilir. Birisi dosya dosya şifreleme, diğeri de tüm arşivin dosya adlarıyla birlikte şifrelemesi. İlkinde istenirse bazı dosyalara şifre verip bazıları açık olarak saklanabilir. Bunun dezavantajı şifrelerin bulunabilir olmasıdır. Örnegin arşivin içerisinde "Alphaville - Big in Japan.mp3" bulunduğunu varsayalım. Şifreyi bulmak isteyen kişi aynı dosyayı internetten bulabilirse (dosya boyutundan aynı olup olmadığı anlaşılabilir) .rar içerisinde şifrelenerek sıkıştırılmış veriyi şifrelemeden sıkıştırılmış veriyle karşılaştırarak şifreyi bulabilir. Arşivdeki diğer dosyaların şifresi aynıysa diğer dosyalar da açılabilir. Bundan korunmanın yolu dosya adlarıyla birlikte tüm arşivi şifrelemektir. İlk durumda WinRAR ile açıldığında dosya adları listelenecek ve şifrelenmiş olanlar yanlarında * ile görüntülecek; ikinci durumda WinRAR ile açmaya kalkar kalkmaz şifre soracak, içeriği göstermeyecektir.

Arşiv içerisindeki her dosya için RARReadHeader çağırılır. Son dosyadan sonra bu fonksiyon çağırıldığında ERAR_END_ARCHIVE döndürür. Arşivin geneli değil de dosya dosya şifreleme yapılmışsa RARReadHeader hata döndürmez onun yerine RARHeaderData'nın Flags alanındaki bir biti set eder. Bir dosyanın başlığı okunduktan sonra RARProcessFile çağırılarak bir sonraki dosyaya geçilir.

  • RARProcessFile: Prototipi,
int PASCAL RARProcessFile(HANDLE hArcData, int Operation, char *DestPath, char *DestName)

şeklindedir. Başlığı RARReadHeader ile okunan dosyanın ne yapılacağı bu fonksiyonun Operation argumanıyla belirlenir. Operation için tanımlı değerler RAR_SKIP, RAR_TEST ve RAR_EXTRACT biçimindedir. Eğer dosya RAR_OM_LIST modunda açılmışsa bütün değerler RAR_SKIP'e eşdeğerdir.

.rar dosyaların .zip'lere göre önemli üstünlüğü katı arşiv (solid archive) oluşturulabilmesi. Arşivlerde dosyalar tek tek sıkıştırılabileceği gibi hepsi bir bütün olarak bir dosyada toplanıp sıkıştırılabilir. Linux'ta gzip de bzip de ayrı ayrı dosyaları sıkıştırmaz yalnızca karakter dizisini (stream) sıkıştırır. Dosyalar tek bir .tar dosya içerisinde birleştirilir (tar sıkıştırmaz) sonra gzip yada bzip ile sıkıştırılır. Benzeri katı arşiv .rar dosyalarda da geçerlidir. Katı arşivin avantajı biraz daha fazla sıkıştırabilir olmasıdır. Örneğin .jpg dosyalar zaten sıkıştırılmış olduklarından ayrı ayrı fazla sıkışmazlar ancak benzer header verilerine sahip .jpg'ler tek bir dosyada birleştirilirse benzer header'lar kolayca sıkıştırılır. Katı arşivin avantajı daha fazla sıkıştırılabilmesi olsa da arşivden bir dosya açmak için kendisinden önce gelen bütün dosyaların açılması gerekmektedir. 

Katı arşiv dosyaları RAR_OM_EXTRACT modunda açılmışsa RAR_SKIP verildiğinde kendisinden önceki dosyalar yine açılır, bu nedenle katı arşivlerde dosyalara bakmak biraz daha zaman alır. 

Eğer tek bir dosya şifrelenmişse açılmaya çalışıldığı zaman fonksiyon ERAR_BAD_DATA döndürür, oysa bu hata CRC hatasına karışılık gelir. Bunun açıklaması büyük olasılıkla şu şekildedir. Dosya sıkıştırılırken CRC'si hesaplanarak yazılır ancak sıkıştırılmış veri çıktısı girilen şifre üzerinden bir işlemden daha geçirilir. Basit olması açısından bunu XOR olarak kabul edelim. Sıkıştırılmış veri, açma (decompression) sırasında girilen şifre üzerinden XOR'lanıp sonra açma algoritmasına sokulur. Sonuçta çıkan verinin CRC'si eğer şifre yanlışsa kaydedilmiş CRC ile uyuşmayacaktır. Şifrenin ayrı olarak arşivin içerisinde kaydedilmeyip karakter dizisi üzerinden işleme sokulmasından dolayı .rar şifreleri yalnızca sözlük tabanlı saldırılarla bulunabilir. Hiçbir dosyasının bilinmediği bir arşiv dosyasın içeriğinden şifresinin bulunması olanaksızdır.

  • RARSetPassword: Prototipi,
void PASCAL RARSetPassword(HANDLE hArcData, char *Password)

şeklindedir. Bu şifreli arşivler için en önemli fonksiyondur. Kullanımı prototipinden anlaşılacağı üzere basittir. 

6 Temmuz 2013 Cumartesi

Eğrisiyle Doğrusuyla Debug.exe


(Bu yazıyı daha önce yazdığım ve kısıtlı bir çevrede dağıttığım bir belgenin küçük bir kısmından derledim. Internet'te bu yazının bazı bölümlerini aynen içeren bir doküman bulursanız o benim olabilir.)

debug.exe, DOS zamanlarından beri herkeste bulunan; Windows'la birlikte hala dağıtılan (32 bit Windows7'de var.) bir araç. Peki ne işe yarar bu araç? Nasıl kullanılır?

Bu araç elbetteki koddaki bug'ları otomatik temizlemiyor. Adından anlaşılacağı üzere bu bir debugger. Yani programın çalışmasını mikroişlemci düzeyinde inceleyip varsa hataları görmek, tek tek komut komut çalıştırıp kısmen de olsa değişiklikler yapmak olanaklı. Windows için güncel debugger'ları takip edemedim ama 2000'lerin başında Win32Dasm ve SoftICE kullanırdık. Bunlara da yeri gelirse kısaca değinirim ancak baştan belirteyim debug.exe DOS1.0 zamanlarında kalma (1980'lerin başından) bir program. 80286 komut setini bile desteklemiyor. EAX, EDI gibi yazmaçlara erişilemiyor. Daha iyi işler yapabilmek için daha güçlü bir debugger'a gereksinimi olanların TASM paketi içerisindeki Turbo Debugger'ı yada FreeDOS'un debugger'ini kullanmaları daha mantıklı.

Komut satırında debug dediğiniz zaman debug'un kendi iptidai - ile belirtilen komut satırına düşülüyor. Debug'dan sonra "debug \WINDOWS\system32\format.com" şeklinde dosya adı da vermek mümkün hatta format.com'un komut satırından aldığı parametreler de dosya adından sonra eklenebilir. Debug.exe'nin komut satırına düştükten sonra kendi tek harfli komutlarını bilmek gerekiyor. Bu komutlar komut satırında ? yazarak ulaşabileceğiniz bir avuç komut. Şimdi bunları sırasıyla açıklayayım.

* a (Assemble): a komutu girildiği zaman debug size CS:0100 (CS: Code Segment. Kodun bellekteki adresini tutan bir yazmaç. Dolayısıyla siz a yazdığınızca CS'nin içeriği kaçsa o sayıyı gösterecek. Bunu işletim sistemi 640Kb'lik bellekteki boş alana göre ayarlar.) adresini gösterir ve sizden x86 assembly dili komutlarını girmenizi bekler. 0100h offset adresi de DOS'ta bir .com çalıştırılabilir dosya yüklendiğinde onun ilk komutunun bulunduğu yerdir. Segmentin 0000h ile 0100h arasında PSP (Program Segment Prefix) adı verilen başlık bulunur. Komut yazma modunda komut yazmadan enter'a basınca çıkılır. a'dan sonra herhangi bir 16 bitlik sayı girilirse komutun girilmesine o satırdan devam edilir.

-a0200
1381:0200 mov bx,b800
1381:0203


* c (Compare): c komutu iki bellek parçasını karşılaştırmak için kullanılır. Yalnızca farklı olan byte'lar gösterilir. İstenirse farklı segmentler de verilebilir. Aşağıdaki örnek 0000:0100h ile 0000:0110h arası parçayı DS:0400h'den itibaren karşılaştırır.

-c 0000:0100 0110 0400
0000:0100  BB  00  1381:0400

0000:0102  B8  00  1381:0402
0000:010A  01  00  1381:040A 
0000:010B  1A  00  1381:040B

* d (Dump): d komutu belleğin bir parçasını görüntüler. Tek parametreyle verilirse segmenti DS olarak seçer  ve 128 byte görüntüler. İki parametreyle verilirse verilen iki offset arasını gösterir. Parametresiz verildiğinde en son görüntülediği bellek parçasının ardışığı 128 byte'ı gösterir. Böylece ardarda verilen d komutlarıyla uzun bir bloğa parça parça bakmak olanaklıdır.

-d0110 011F
1383:0110  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................



* e (Enter): e komutuyla bir bellek bölgesine elle byte'lar iki türlü girilebilir:

Aşağıdaki komutla DS:0120h'den itibaren 13h, 16h, 19h ve 1Ch byte'ları sırasıyla yazılır.
-e 0120 13 16 19 1C

Aşağıdaki komutla ES:0200h'dan itibaren 13h, 16h, 19h ve 1Ch byte'ları tek tek yazılır. Sayılar girildikten sonra boşluk karakterine basmak gerekir:
-e ES:0200
1381:0200  00.13   00.16   00.19   00.1C

* f (Fill): f komutu bellek bölgesini verilen karakterlerle belli uzunlukta doldurur.

Aşağıdaki komutla 0100h offsetinden başlayarak 12h, 13h, 14h byte sırası 16 byte boyunca tekrarlanır:
-f 0100L10 12 13 14

* g (Go): g komutu, CS:IP ile gösterilen bellek adresinde hangi komutlar varsa koşulsuz olarak çalıştırır. Program sonlanıp yada kesilip çalışmayı debug.exe'ye geri bırakana kadar sürer. g'den sonra bir bellek offset adresi verilirse debug onu verilen bellek adresine kadar çalıştırıp ardından r komutunu uygular ve tekrar debug.exe'nin komut satırına düşer. Bu bellek adresine breakpoint denir. Debugger'larda breakpoint'ler belirlenerek program parça parça çalıştırılabilir. g komutuna ayrı bir parametre vermek yerine çalışması kesime uğratılacak noktadaki kod parçası 0CCh (INT 3) ile değiştirilebilir.

g komutu için söylenmesi gereken önemli bir şey daha var: Eğer program DOS 1.0 zamanlarından kalma INT 20h kesmesini kullanarak çıkıyorsa debug.exe "Program terminated normally." diyor ve program sorunsuz debug.exe'ye düşüyor. Yanılmıyorsam bu INT 21h,00h için de geçerli. Ancak DOS 2.0 ve sonrası için önerilen program sonlandırma yöntemi INT 21h,4Ch çağırıldığı zaman bu yöntem debug.exe'yi de sonlandırıp DOS komut satırına düşmenize neden olabiliyor (özellikle birden fazla kere çağırıldığında).

* h (Hex): h komutu verilen iki sayıyı toplar ve çıkarır.

-h 2D1 2E8
05B9  FFE9


* i (Input): Bir porttan veri okur. Komutu port numarası takip eder. Windows NT tabanlı tüm işletim sistemlerinde bu veriye pek güvenmemek gerekiyor. Doğrudan portlara erişmek bazı durumlarda olanaksız, örneğin Windows'un diski kontrol eden sürücüsü diske erişmenize izin vermez. Basit görece zararsız portlar (klavye gibi) işletim sistemi üzerinden erişilebilir durumdalar.

-i 60
1C


60h portu klavye denetleyicisinin portudur.

* l (Load): l komutunun iki kullanımı bulunmaktadır. Birinci kullanımda mantıksal bir sektörü (cluster) belleğe okumakta kullanılır. Bunun kullanımı Windows NT tabanlı sistemlerde yine kısıtlı. XP'de yanlış hatırlamıyorsam çekirdek sürücüleri üzerinden okuma yapılabiliyordu, yazmaya izin yoktu. Windows 7'de şöyle şeyler oluyor:

Bu diyalog kutusunda Close diyince debug.exe sonlandırılıyor, Ignore diyince "Disk error reading drive C" hatası veriyor.

Komutun kullanımında, l'den sonra gelen parametre cluster'ın okunacağı bellek adresi, 02h disk sıra numarası. ( 00h = A: , 01h = B:, 02h = C:, 03h = D: ... ) Sonraki sayılar cluster numarası ve okunacak toplam cluster sayısı.

İkinci kullanımında n ile belirtilen dosyayı (çalıştırılabilir olması şart değil) CS:0100h adresine yükler. Eğer dosya bir .exe dosya ise yüklenecek yer CS:0100h olmayabilir. Bu kullanımla ilgili n komutunun açıklamasına da bakılabilir.

* m (Move): m komutu bir bellek parçasını başka bir bellek parçasının üstüne kopyalar.

DS:0200h'dan DS:0400h'a 128 byte kopyala:
-m 0200 l80 0400

* n (Name): n komutu bir dosya yada parametrelerini debug.exe'ye bildirmeye yarar. l ile bir dosya okumak yada w ile bir dosya yazılmak istendiğinde önce n komutuyla dosya bildirilmesi gerekmektedir. Komut satırında debug'dan sonra dosya adı verilebileceğini belirtmiştim. Eğer bu dosya adı komut satırından verilmezse n komutuyla verilip sonra l komutuyla dosya yüklenebilir. n komutu dosyanın olup olmadığını kontrol etmez, yalnızca bir FCB (File Control Block) oluşturur. l komutu da bu FCB'deki dosyayı açmaya çalışır. Dolayısıyla n komutuna ne girilirse girilsin hata vermeyecektir. İlgili hata l komutu verildiği zaman alınır.

-n\Windows\winhlp32.exe
-l


Yazmaya ilgili bilgiler w komutunda daha etraflıca anlatılacaktır.

* o (Output): Bir porta veri yazar. i komutu gibi bunda da komutu port numarası takip eder. Port numarasından sonra porta gönderilecek veri yazılır. Yine Windows NT tabanlı tüm işletim sistemlerinde i komutundaki gibi güvenlik vb. nedenlerden bu komutun sonucuna pek güvenmemek gerekiyor. Veri porta gönderilmemiş olabilir yada güvenlik hatasıyla debug.exe sonlandırılabilir.

71h portu CMOS adresleme, 70h portu da okuma portudur. Aşağıdaki komutlarla CMOS'un 19h byte'ı okunur:
-o 71 19
-i 70
DD

* p (Proceed): p komutu tek başına kullanıldığında CS:IP'teki bir komutu çalıştırır ancak eğer CS:IP'deki komut bir altprogram yada kesme çağrısıysa (CALL yada INT) içine girmeden tamamını çalıştırılır ve çalışmanın sonunda yazmaçların içeriklerini gösterir (r komutunun çıktısı).

p komutundan sonra bir sayı girilirse (p 3 gibi) debug.exe o kadar sayıda komutu çalıştırarak tek tek her seferinde yazmaç içeriklerini gösterir.

p komutundan sonra başında = bulunan bir offset adresi girildiği zaman IP (Instruction Pointer) bu offset adresine gelinceye kadar program çalışır. O adreste komut bitmiyorsa komutun bittiği son yerde çalışma durur. Yani IP'nin son değeri bu değere eşit yada büyüktür.

-p =0107

AX=0900  BX=FFFF  CX=FE00  DX=0000  SP=00B8  BP=0000  SI=0000  DI=0000
DS=13DA  ES=13DA  SS=13EA  CS=13EA  IP=0107   NV UP EI PL NZ NA PO NC
13EA:0107 CD21          INT     21


* q (Quit): q komutu debug.exe'den çıkar. Herhangi bir parametresi yoktur, verilse de dikkate alınmaz. Yani q, quit yada quake yazılsa da debug.exe'den çıkar.

* r (Register): r komutu yazmaç içeriklerini görüntülemek ve değiştirmek için kullanılır. r komutu tek başına bütün yazmaçları görüntüler.

-r
AX=0900  BX=FFFF  CX=FE00  DX=0000  SP=00B8  BP=0000  SI=0000  DI=0000
DS=13DA  ES=13DA  SS=13EA  CS=13EA  IP=0007   NV UP EI PL NZ NA PO NC
13EA:0007 CD21          INT     21

Çıktıyı kısaca açıklarsam, AX, BX, CX ve DX x86 serisi işlemcilerin çeşitli komutlarla kullandığı genel amaçlı yazmaçlardır. Accumulator, Base, Counter, Data gibi açılımları olsa da ( http://en.wikipedia.org/wiki/AX_register#Purpose ) genel olarak bunların içeriklerini değiştirmenin komutlar işlemediği sürece bir etkisi yoktur.

SP, stack pointer anlamına gelir ve işlemcinin o anda kullandığı stack'in (yığın) offset adresini tutar. BP, base pointer anlamına gelir ve göstericilerin tutulması için yardımcı bir yazmaçtır. Genelde argümanlarına yığın üzerinden erişen C gibi yüksek seviyeli dillerde fonksiyonların en başında şuna benzer bir kod bloğu bulunur:

k = fonk1(0x0202, 0x0101);

karşılığında üretilen kod:

mov ax, 0101
push ax
mov ax, 0202
push ax
call @fonk1

@fonk1:
push bp
mov bp,sp
...
pop bp
ret

biçimindedir. Alt program blogunda kısa (segment içi) çağrı için ilk arguman [bp+04] ikincisi [bp+06] ve uzun (segment dışı) çağrı için ilk arguman [bp+06] ve ikincisi [bp+08] adresinde bulunur. Yani alt programda BP yedek bir SP işi görür ama elbette ki bu bir zorunluluk değildir. SP genelde doğrudan değiştirilmez, push ve pop gibi komutlarla değeri kendiliğinden değişir.

SI, source index ve DI destination index anlamına gelir. MOVS, STOS, SCAS ... gibi karakter dizisi üzerinden yapılan işlemlerde okumalar DS:SI üzerinden, yazma işlemleriyse ES:DI üzerinden yapılır.

DS, data segment anlamına gelir. Genelde veriler üzerinde yapılacak işlemlerde verilerin tutulduğu segment DS'de bulunur. CS, code segment anlamındadır. CS, IP (instruction pointer) yazmacıyla birlikte mikroişlemcinin o an çalıştıracağı komutu tutar. SS'in anlamı stack segment'tir adından da anlaşılacağı üzere SP ile birlikte yığını gösterir. Son olarak ES, extra segment'dir ve bütün bu segment yazmaçlarının haricinde bir yazmaça ihtiyaç duyulması durumunda yardımcı segment yazmacı olarak kullanılır. IP yazmacı programcı tarafından yalnızca jmp ve koşullu dallanma komutlarıyla değiştirilebilir. Her komutun çalıştırılması sonucunda bir sonraki komutu gösterecek şekilde mikroişlemci tarafından değiştirilir. Örneğin 2 byte'lık bir komutun çalıştırılması sonucunda değeri 2, 3 byte'lık bir komutun çalıştırılması sonucunda değeri 3 arttırılır.

NV UP gibi ifadeler FLAGS yazmacına aittir. Bununla ilgili Temmuz 2012 tarihinde "Peki Neden Trap Flag?" başlıklı yazıda ayrıntılı bilgi bulunuyor. En alt satırdaysa o anki CS:IP'nin değeri ve o değerin gösterdiği yerdeki mikroişlemci komutları ve bunların Assembly karşılığı bulunur.

r komutuyla yazmacın içeriğinin değiştirilmesi için r'den sonra yazmacın adı yazılır. Enter'a basıldığında yazmaçtaki o anki değer görüntülenir ve ardından yeni değer girilmesi beklenir. Herhangi bir değer girmeden enter'a basılırsa yazmacın içeriği değişmez. FLAGS yazmacının içeriğine rf komutuyla erişilebilir ve içerik bu komutla değiştirilebilir.

-rax
AX 0202
:0303


* s (Search): s komutu başlangıcı ve bitişi verilen bir aralıkta verilen bir byte yada byte dizisini aramaya yarar.

Aşağıdaki komut DS:0100h ile 0200h arasındaki bütün 0b8h 00h 01h dizisini listeler:
-s 0100 0200 b8 00 01
1381:0100
1381:01A4


İstenirse aranacak veri çift tırnak içerisinde belirtilerek karakterle arama da yapılabilir:
-s 0100 0200 "h"

* t (Trace): t komutu da p komutu gibi tek başına kullanıldığında CS:IP'teki bir komutu çalıştırır ancak p'den farklı olarak t komutu, CALL yada INT gibi komutların içerisine girerek onları da çalıştırır. Çıktı olarak p gibi yazmaç içeriklerini gösterir. t komutu da p komutu gibi komut sayısı belirtilerek yada başında = olan bir offset adresiyle kullanılabilir. Parametrelerin etkisi de p komutuyla aynıdır.

* u (Unassemble): u komutu belleğin bir parçasındaki byte'ları alarak bunları Assembly kodlarına geri çevirir ve ekrana sığdığı kadarıyla listeler. Eğer tek parametreyle verilirse bu değeri offset değeri olarak alıp listelemeye bu offsetten başlar. İki parametreyle verildiğinde birinci değer başlangıç ikinci değer bitiş olmak üzere aradaki kodlar listelenir. Parametresiz verildiğinde en son görüntülediği kod bloğunun bitişiğindeki bloğu listeler. Ardarda verilen u komutlarıyla uzun bir bloğa parça parça bakmak olanaklıdır.

* w (Write): w komutunun da l komutu gibi iki kullanımı bulunmaktadır. l ye benzer olarak bellekten mantıksal sektörlere yazılabilir. (Windows NT de kısıtlamalar var.) Bu kullanımda parametreler l komutuyla aynıdır.

Dosya yazdırılacaksa yine n komutuyla birlikte kullanılır, kaç byte yazılacağı da BX:CX yazmaçlarında tutulur. Yazılacak verinin büyüklüğü 16lık sayı sistemine çevirilir. Düşük değerli 4 basamak CX'te yüksek değerli 4 basamak BX'de olmak üzere 32 bite kadar büyüklükte dosyalar yazılabilir.

Son olarak bunlardan başka X ile başlayan EMS ile ilgili komutlar bulunsa da EMS konusunda bilgim olmadığından bunlara değinmeyeceğim.


Dipnot: Yeni yazı maalesef yine çok uzunca bir zaman gecikti. Mayıs ortasında diskim bozuldu, sonrasında önemli bir sınava girdim ki yedekleme, işletim sistemi kurma, geri yüklemeyle ilgilenmekten sınava doğru dürüst çalışamadım bile. Haziran başında ülkenin içerisinde bulunduğu siyasi durumu izlemekten iş yerinde dahi doğru dürüst çalışamadım. Haziran ortasında bir daha diskim bozuldu falan filan derken Nisan'ın 13ünde başladığım yazıyı tamamlamam bugünü buldu. Gecikmeden ötürü herkesten özür dilerim.