Geçen hafta Calibro'dan bir e-kitap satın aldım. Cynthia Stokes Brown'ın Büyük Tarih: Büyük Patlamadan Bugüne adlı kitabını. Satın aldım, indirdim, açmaya çalıştım — ve işte o zaman eğlence başladı.
Kitap açılmıyordu. Daha doğrusu, sadece Calibro'nun kendi uygulamasında açılıyordu. Bu uygulamayı telefona kurmayı denedim, Android telefonumda uygulama güncel değil gibi bir hata verdi ve çalışmadı. Bilgisayarda okumayı denedim, olmadı. Satın aldığım bir şeyi okuyamıyordum.
Calibro'ya mail attım. Tweet attım. Yanıt vermediler.
O gece bir karar verdim: Bu kitabı okuyacaktım.
DRM Nedir?
DRM (Digital Rights Management), dijital içerikleri korumak için kullanılan şifreleme sistemidir. Teoride mantıklıdır: İçerik üreticilerinin haklarını korur. Pratikte ise satın aldığın bir şeyi kullanamamana neden olur.
Calibro, PocketBook'un PBDRM sistemini kullanıyor. Kitap .lndrm uzantılı bir dosya olarak geliyor. İçini hex editörle açınca şunu gördüm:
50 42 44 52 4D ... → "PBDRM" magic bytes
Adobe ADEPT altyapısı üzerine kurulu, RSA + AES-128-CBC şifreleme kullanıyor. Yani:
- Kitabın içeriği AES-128-CBC ile şifrelenmiş
- AES anahtarı RSA ile şifrelenmiş
- RSA şifrelemek için Calibro'nun sunucu tarafındaki public key kullanılmış
- Çözmek için Calibro'nun elindeki private key gerekiyor
İlk Hamle: Adobe Digital Editions
İlk denediğim şey Adobe Digital Editions (ADE) ve DeDRM plugin'iydi. Bu ikili çoğu e-kitap DRM'ini kaldırabiliyor. Ama ADE denemeye başlamadan önce şöyle bir hata verdi:
E_ADEPT_NO_TOKEN
Bu hata, ADE'nin Adobe'nin lisans sunucusundan (adeactivate.adobe.com) token alamaması anlamına geliyor. Çözüm denemeleri:
- ADE'yi deauthorize edip yeniden authorize etmek
- VPN'i kapatmak
- Hosts dosyasında Adobe sunucularının bloklanıp bloklanmadığını kontrol etmek (
/etc/hostsveyaC:\Windows\System32\drivers\etc\hosts) - ADE'yi güncellemek
Hiçbiri işe yaramadı. Üstelik DeDRM plugin, PBDRM formatını zaten tanımıyordu — bu Adobe'nin standart ADEPT formatı değil, PocketBook'un özel bir implementasyonu.
Memu Emülatörü
Calibro Reader yalnızca Android'de çalışıyor ve güncel telefonlarda çalışmıyor. Çözüm: Memu — Windows'ta Android uygulamaları çalıştıran bir emülatör.
Memu → Android x86_64 emülatör
Calibro Reader APK → ARM binary
Uygulama kuruldu ve kitap açıldı. Bu aşamada kitabı sadece Memu altında okuyabilecektim, aradığım çözüm bu değildi elbette.
PBDRM Header Analizi
Kitap dosyasını Python ile incelemeye başladım:
data = open('kitap.lndrm', 'rb').read()
print(data[:32].hex())
# 5042 4452 4d00 0100 a896 3191 69d0 5969...
# P B D R M
Header yapısı:
| Offset | Uzunluk | İçerik |
|---|---|---|
| 0x00 | 5 byte | PBDRM magic |
| 0x05 | 2 byte | 0x0001 type |
| 0x07 | 2 byte | 0x00a8 = 168 (şifreli anahtar uzunluğu) |
| 0x09 | 168 byte | RSA ile şifreli AES anahtarı |
| 0x120 | 16 byte | IV (initialization vector) |
| 0x130 | ~ | AES ile şifreli içerik |
168 byte ilginçti — RSA-1024 için 128 byte olması gerekir. Fazladan 40 byte'ta ne var? Bunun üzerinde saatlerce çalıştım: IV mi, salt mı, başka bir şey mi?
activation.xml
Uygulamanın data dizininde kritik dosyalar vardı:
adb shell "ls /data/data/com.calibro.reader/app_resources/"
# activation.xml
# device.xml
activation.xml Adobe ADEPT formatındaydı. İçinde iki önemli şey:
- privateLicenseKey: RSA-1024 private key (base64 encoded)
- pkcs12: Şifreli PKCS12 sertifikası
import base64, xml.etree.ElementTree as ET
from Crypto.PublicKey import RSA
tree = ET.parse('activation.xml')
pk_b64 = tree.find('.//{http://ns.adobe.com/adept}privateLicenseKey').text
pk_der = base64.b64decode(pk_b64)
rsa_key = RSA.import_key(pk_der)
print(f"Key boyutu: {rsa_key.size_in_bytes()} bytes") # 128
privateLicenseKey ile .lndrm dosyasındaki 168 byte'lık bloğu çözmeye çalıştım. 168 byte içinde 128 byte'lık RSA bloğunun hangi offset'te olduğunu bulmak için brute force:
from Crypto.Cipher import PKCS1_v1_5
full_block = data[0x09:0x09+168]
for offset in range(0, 41):
rsa_block = full_block[offset:offset+128]
cipher = PKCS1_v1_5.new(rsa_key)
result = cipher.decrypt(rsa_block, b'\xff' * 32)
if result != b'\xff' * 32:
print(f"[+] offset={offset}: {result.hex()}")
Hiçbir offset'te başarılı decrypt elde edemedim. Neden? Çünkü privateLicenseKey kullanıcı kimlik doğrulama anahtarıydı — kitap şifreleme anahtarı değildi. Gerçek şifre çözme anahtarı Calibro'nun sunucusundaydı.
PKCS12 sertifikasının şifresini kırmayı denedim — cihazın fingerprint'i, device ID, salt gibi değerleri şifre olarak denedim. Hepsi başarısız oldu.
Frida ile Hook Denemesi
Eğer anahtarı statik analiz ile bulamıyorsam, uygulama çalışırken dinamik olarak yakalamayı deneyebilirdim. Frida bunun için ideal araç — çalışan bir process'e JavaScript kodu enjekte etmeyi sağlıyor.
Plan: libpb_reader.so veya libadobe_rmsdk.so içindeki AES/RSA fonksiyonlarını hook'layıp şifre çözme anında anahtarı yakalamak.
Java.perform(function() {
var EVP_DecryptInit_ex = Module.findExportByName(
"libpb_reader.so",
"EVP_DecryptInit_ex"
);
Interceptor.attach(EVP_DecryptInit_ex, {
onEnter: function(args) {
var key = args[3].readByteArray(16);
console.log("[+] AES KEY: " + bytesToHex(key));
}
});
});
Ama Frida modülleri listelerken libpb_reader.so ve libadobe_rmsdk.so hiç görünmüyordu. Neden? Houdini.
Houdini Nedir?
Memu x86_64 tabanlı bir emülatör. Calibro Reader ise ARM binary. Intel'in Houdini katmanı ARM kodunu gerçek zamanlı olarak x86'ya çevirerek çalıştırıyor. Bu işlem Frida'nın göremediği ayrı bir memory space'de gerçekleşiyor.
Frida → x86_64 process görüyor
Houdini → ARM kütüphanelerini kendi içinde çalıştırıyor
libpb_reader.so (ARM) → Frida'ya görünmüyor
Memu'yu ARM moduna geçirdim (System Settings → Preferences → ABI → ARM). Fark etmedi — Android sistemi x86_64 kalmaya devam etti, Frida hala ARM kütüphanelerine ulaşamadı.
/proc/pid/mem — Memory Dump Denemesi
Belki şifresi çözülmüş veriyi doğrudan RAM'den okuyabilirdim. EPUB dosyaları ZIP formatındadır, magic bytes: PK\x03\x04. Uygulama kitabı render ettiğinde bu veri bir an için RAM'de olmalı.
frida-ps -U | grep calibro
# 5254 com.calibro.reader:viewer
adb shell "su 0 cat /proc/5254/maps" > maps.txt
maps.txt'e bakınca libpb_reader.so ve libadobe_rmsdk.so ARM binary olarak yüklenmiş görünüyordu. Heap bölgelerini dump etmeye çalıştım:
cmd = f'adb shell "su 0 dd if=/proc/5254/mem bs=4096 skip={start//4096} count={size//4096}"'
# Sonuç: boş
/proc/pid/mem okuma bu Memu kurulumunda root ile bile çalışmıyordu.
Sonuç: DRM Kırılamazdı.
Şifre çözme anahtarı Calibro'nun sunucusunda. Cihaza hiçbir zaman tam olarak gönderilmiyor. Native ARM kod (Houdini altında) onu alıp kullanıyor, dışarıya sızdırmıyor. Frida ARM kütüphanelerine erişemiyor.
Bu noktada stratejimi değiştirdim.
Plan B: Ekran Görüntüsü Otomasyonu
Uygulama kitabı ekranda gösteriyordu. Ekranda olan her şey yakalanabilir :)
İlk deneme:
adb shell screencap -p /sdcard/test.png
Sonuç: Tamamen beyaz, boş PNG. Neden? FLAG_SECURE. Bu Android flag'i ekran görüntüsü alınmasını engeller.
Ama Memu'nun kendi screenshot mekanizması bu flag'i atlıyordu. Peki bunu ADB ile tetikleyebilir miydim?
adb shell "input keyevent 120"
# 120 = KEYCODE_SYSRQ = screenshot keycode
Evet! Bu komut FLAG_SECURE'u atlıyordu. Sayfa çevirmek için de klavye ok tuşu çalışıyordu:
adb shell "input keyevent 22"
# 22 = KEYCODE_DPAD_RIGHT = sağ ok tuşu
Ekrana tap bazı sayfalarda dipnot linklerine denk geliyordu. Klavye tuşu bu sorunu çözdü.
Otomasyon Scripti
import subprocess, time, os
output_dir = r'C:\Users\hicbi\Downloads\kitap_pages'
os.makedirs(output_dir, exist_ok=True)
popup_wait = 10.0
page_turn_delay = 1.5
def adb(cmd):
subprocess.run(f'adb shell {cmd}', shell=True, capture_output=True)
def get_latest_screenshot():
result = subprocess.run(
'adb shell "ls -t /sdcard/Pictures/Screenshots/"',
shell=True, capture_output=True, text=True
)
files = [f.strip() for f in result.stdout.strip().split('\n') if f.strip()]
return files[0] if files else None
def capture_page(page_num):
local = os.path.join(output_dir, f'page_{page_num:04d}.png')
adb('"input keyevent 120"')
time.sleep(popup_wait)
latest = get_latest_screenshot()
subprocess.run(
f'adb pull "/sdcard/Pictures/Screenshots/{latest}" "{local}"',
shell=True, capture_output=True
)
def next_page():
adb('"input keyevent 22"')
time.sleep(page_turn_delay)
for i in range(1, 283):
capture_page(i)
if i < 282:
next_page()
Karşılaşılan Sorunlar ve Çözümleri
1. Screenshot popup'ı
Her screenshot sonrası "Screenshot saved" popup'ı çıkıyordu. ADB ile kapatmayı denedim (input tap, keyevent 4 vs.) — olmadı. Çözüm: Popup 7-10 saniye sonra otomatik kapanıyor, popup_wait = 10 yaptım.
2. Dipnot linkleri
input tap 1200 450 koordinatı bazı sayfalarda dipnot linkine denk geliyordu. Çözüm: input keyevent 22.
3. Screenshots klasörünün dolması
Yüzlerce PNG birikince ls -t yavaşladı, timing bozuldu. Çözüm: Her 50 sayfada bir temizle:
adb shell "rm /sdcard/Pictures/Screenshots/*"
4. Windows uyku modu
Uzun süre hareketsiz kalınca Memu dondu. Çözüm: Güç ayarlarından uyku modunu "Hiçbir zaman" yap. Android için:
adb shell "settings put system screen_off_timeout 3600000"
5. EPUB sayfa numarası tutarsızlığı
282 sayfalık kitap 413 ekran görüntüsüne çıktı. EPUB'da sayfa numarası font boyutuna ve ekran boyutuna göre değişiyor.
6. Performans şişmesi
Uzun süreli çalışmada Memu RAM'i doldu. Çözüm: 50'şer sayfa batch'ler halinde çalıştır.
OCR: Görüntüden Metne
413 ekran görüntüsü elimdeydi. Tesseract OCR ile metni çıkardım:
- github.com/UB-Mannheim/tesseract adresinden Windows installer
- Kurulum sırasında "Additional language data" → Turkish seçilmeli
pip install pytesseract pillow
import pytesseract
from PIL import Image
import os
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
folder = r'C:\Users\hicbi\Downloads\kitap_pages'
files = sorted([f for f in os.listdir(folder) if f.endswith('.png')])
with open('buyuk_tarih.txt', 'w', encoding='utf-8') as out:
for i, filename in enumerate(files):
img = Image.open(os.path.join(folder, filename))
text = pytesseract.image_to_string(img, lang='tur')
out.write(text)
out.write(f'\n\n--- Sayfa {i+1} ---\n\n')
Türkçe karakter tanıma (ş, ğ, ü, ö, ı, ç) neredeyse hatasız çalıştı.
Sonuç Olarak
Bir kitap için 10+ saat harcadım. Sırasıyla şunları denedim:
- ✗ Adobe Digital Editions + DeDRM plugin → PBDRM formatını tanımıyor
- ✗ PBDRM header analizi + RSA brute force → Yanlış anahtar
- ✗ PKCS12 şifre kırma → Şifre bulunamadı
- ✗ Frida hook → Houdini ARM emülasyonu engel
- ✗ /proc/pid/mem dump → İzin yok
- ✓ ADB screenshot otomasyonu → Çalıştı
DRM sistemi beni değil, içerik üreticisini korudu. Ben yasal olarak satın aldığım bir içeriği okumak için bu kadar çaba harcadım. Korsanlık yapan biri bu adımların hiçbirini atmak zorunda kalmaz — DRM kırılmış versiyonunu internetten bulur.
Belki de DRM'in asıl kurbanları dürüst kullanıcılardır.
Kullanılan Araçlar
| Araç | Amaç |
|---|---|
| Memu | Android emülatör |
| ADB (Android Debug Bridge) | Cihaz kontrolü |
| Frida | Dynamic instrumentation |
| Python + pycryptodome | RSA/AES analizi |
| Tesseract OCR | Görüntüden metin |
| Hex editör | PBDRM format analizi |
Bu yazı tamamen kişisel kullanım amacıyla yapılan bir deneyin belgesidir. Hiçbir içerik üçüncü şahıslarla paylaşılmamıştır.
