SVG/Font Glyph Analysis & Web DRM Deobfuscation (Raster Hashing + SSIM)
Tip
Μάθετε & εξασκηθείτε στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Αυτή η σελίδα τεκμηριώνει πρακτικές τεχνικές για την ανάκτηση κειμένου από web readers που αποστέλλουν positioned glyph runs μαζί με per-request vector glyph definitions (SVG paths), και που τυχαίοποιούν τα glyph IDs ανά request για να αποτρέψουν το scraping. Η βασική ιδέα είναι να αγνοήσουμε τα request-scoped numeric glyph IDs και να fingerprint-άρουμε τα οπτικά σχήματα μέσω raster hashing, και στη συνέχεια να αντιστοιχίσουμε τα σχήματα σε χαρακτήρες με SSIM έναντι ενός reference font atlas. Η ροή εργασίας γενικεύεται πέρα από Kindle Cloud Reader σε οποιονδήποτε viewer με παρόμοιες προστασίες.
Warning: Only use these techniques to back up content you legitimately own and in compliance with applicable laws and terms.
Απόκτηση (παράδειγμα: Kindle Cloud Reader)
Παρατηρούμενο endpoint:
- [https://read.amazon.com/renderer/render]
Απαιτούμενα υλικά ανά session:
- Browser session cookies (normal Amazon login)
- Rendering token from a startReading API call
- Additional ADP session token used by the renderer
Συμπεριφορά:
- Κάθε request, όταν αποστέλλεται με browser-equivalent headers και cookies, επιστρέφει ένα TAR archive περιορισμένο σε 5 pages.
- Για ένα μεγάλο βιβλίο θα χρειαστείτε πολλά batches· κάθε batch χρησιμοποιεί διαφορετικό randomized mapping των glyph IDs.
Τυπικά περιεχόμενα TAR:
- page_data_0_4.json — positioned text runs as sequences of glyph IDs (not Unicode)
- glyphs.json — per-request SVG path definitions for each glyph and fontFamily
- toc.json — table of contents
- metadata.json — book metadata
- location_map.json — logical→visual position mappings
Παράδειγμα δομής page run:
{
"type": "TextRun",
"glyphs": [24, 25, 74, 123, 91],
"rect": {"left": 100, "top": 200, "right": 850, "bottom": 220},
"fontStyle": "italic",
"fontWeight": 700,
"fontSize": 12.5
}
Παράδειγμα εγγραφής στο glyphs.json:
{
"24": {"path": "M 450 1480 L 820 1480 L 820 0 L 1050 0 L 1050 1480 ...", "fontFamily": "bookerly_normal"}
}
Σημειώσεις σχετικά με anti-scraping path tricks:
- Paths μπορεί να περιλαμβάνουν micro relative moves (π.χ.,
m3,1 m1,6 m-4,-7) που συγχέουν πολλούς vector parsers και naïve path sampling. - Πάντα αποδώστε γεμάτα, πλήρη paths με έναν αξιόπιστο SVG engine (π.χ., CairoSVG) αντί να κάνετε command/coordinate differencing.
Γιατί αποτυγχάνει η απλή αποκωδικοποίηση
- Per-request randomized glyph substitution: η αντιστοίχιση glyph ID→character αλλάζει κάθε batch· τα IDs δεν έχουν παγκόσμιο νόημα.
- Η άμεση σύγκριση συντεταγμένων SVG είναι ευαίσθητη: ίδια σχήματα μπορεί να διαφέρουν σε αριθμητικές συντεταγμένες ή στην κωδικοποίηση εντολών ανά αίτηση.
- Το OCR σε απομονωμένα glyphs αποδίδει φτωχά (≈50%), μπερδεύει σημεία στίξης και ομοιάζοντα glyphs, και αγνοεί ligatures.
Ροή εργασίας: request-agnostic κανονικοποίηση και αντιστοίχιση glyphs
- Rasterize per-request SVG glyphs
- Δημιουργήστε ένα ελάχιστο SVG έγγραφο ανά glyph με το παρεχόμενο
pathκαι αποδώστε σε ένα σταθερό canvas (π.χ., 512×512) χρησιμοποιώντας CairoSVG ή έναν ισοδύναμο engine που χειρίζεται σύνθετες ακολουθίες path. - Αποδώστε γεμάτο μαύρο σε λευκό· αποφύγετε strokes για να εξαλείψετε artifacts εξαρτώμενα από τον renderer και το AA.
- Perceptual hashing for cross-request identity
- Υπολογίστε ένα perceptual hash (π.χ., pHash via
imagehash.phash) για κάθε εικόνα glyph. - Χρησιμοποιήστε το hash ως σταθερό ID: το ίδιο οπτικό σχήμα σε διαφορετικά requests συμπτύσσεται στο ίδιο perceptual hash, εξουδετερώνοντας randomized IDs.
- Reference font atlas generation
- Κατεβάστε τα στοχευόμενα TTF/OTF fonts (π.χ., Bookerly normal/italic/bold/bold-italic).
- Αποδώστε υποψήφιους για A–Z, a–z, 0–9, σημεία στίξης, ειδικά σημάδια (em/en dashes, quotes) και ρητές ligatures:
ff,fi,fl,ffi,ffl. - Κρατήστε ξεχωριστά atlases ανά παραλλαγή font (normal/italic/bold/bold-italic).
- Χρησιμοποιήστε proper text shaper (HarfBuzz) αν θέλετε glyph-level fidelity για ligatures· απλή rasterization μέσω Pillow ImageFont μπορεί να είναι επαρκής αν αποδώσετε τα ligature strings απευθείας και το shaping engine τα επιλύει.
- Visual similarity matching with SSIM
- Για κάθε άγνωστη εικόνα glyph, υπολογίστε SSIM (Structural Similarity Index) απέναντι σε όλες τις υποψήφιες εικόνες σε όλα τα atlases των παραλλαγών font.
- Αντιστοιχίστε το χαρακτήρα της καλύτερης βαθμολογημένης ταύτισης. Το SSIM απορροφά μικρές διαφορές anti-aliasing, κλίμακας και συντεταγμένων καλύτερα από pixel-exact συγκρίσεις.
- Edge handling και ανακατασκευή
- Όταν ένα glyph αντιστοιχεί σε ligature (πολλαπλοί χαρακτήρες), επεκτείνετέ το κατά την αποκωδικοποίηση.
- Χρησιμοποιήστε run rectangles (top/left/right/bottom) για να συμπεράνετε διακοπές παραγράφων (Y deltas), στοίχιση (X patterns), style και μεγέθη.
- Σειριοποιήστε σε HTML/EPUB διατηρώντας
fontStyle,fontWeight,fontSizeκαι εσωτερικούς συνδέσμους.
Implementation tips
- Κανονικοποιήστε όλες τις εικόνες στο ίδιο μέγεθος και σε grayscale πριν από hashing και SSIM.
- Κάντε cache ανά perceptual hash για να αποφύγετε επανυπολογισμό SSIM για επαναλαμβανόμενα glyphs σε διαφορετικά batches.
- Χρησιμοποιήστε υψηλής ποιότητας raster size (π.χ. 256–512 px) για καλύτερο διαχωρισμό· κάντε downscale όπως χρειάζεται πριν το SSIM για επιτάχυνση.
- Αν χρησιμοποιείτε Pillow για απόδοση TTF υποψηφίων, ορίστε το ίδιο μέγεθος canvas και κεντράρετε το glyph· προσθέστε padding για να αποφύγετε clipping ascenders/descenders.
Python: end-to-end glyph normalization and matching (raster hash + SSIM)
```python # pip install cairosvg pillow imagehash scikit-image uharfbuzz freetype-py import io, json, tarfile, base64, math from PIL import Image, ImageOps, ImageDraw, ImageFont import imagehash from skimage.metrics import structural_similarity as ssim import cairosvgCANVAS = (512, 512) BGCOLOR = 255 # white FGCOLOR = 0 # black
— SVG -> raster —
def rasterize_svg_path(path_d: str, canvas=CANVAS) -> Image.Image:
Build a minimal SVG document; rely on CAIRO for correct path handling
svg = f’‘’‘’’ png_bytes = cairosvg.svg2png(bytestring=svg.encode(‘utf-8’)) img = Image.open(io.BytesIO(png_bytes)).convert(‘L’) return img
— Perceptual hash —
def phash_img(img: Image.Image) -> str:
Normalize to grayscale and fixed size
img = ImageOps.grayscale(img).resize((128, 128), Image.LANCZOS) return str(imagehash.phash(img))
— Reference atlas from TTF —
def render_char(candidate: str, ttf_path: str, canvas=CANVAS, size=420) -> Image.Image:
Render centered text on same canvas to approximate glyph shapes
font = ImageFont.truetype(ttf_path, size=size) img = Image.new(‘L’, canvas, color=BGCOLOR) draw = ImageDraw.Draw(img) w, h = draw.textbbox((0,0), candidate, font=font)[2:] dx = (canvas[0]-w)//2 dy = (canvas[1]-h)//2 draw.text((dx, dy), candidate, fill=FGCOLOR, font=font) return img
— Build atlases for variants —
FONT_VARIANTS = { ‘normal’: ‘/path/to/Bookerly-Regular.ttf’, ‘italic’: ‘/path/to/Bookerly-Italic.ttf’, ‘bold’: ‘/path/to/Bookerly-Bold.ttf’, ‘bolditalic’:‘/path/to/Bookerly-BoldItalic.ttf’, } CANDIDATES = [ *[chr(c) for c in range(0x20, 0x7F)], # basic ASCII ‘–’, ‘—’, ‘“’, ‘”’, ‘‘’, ‘’’, ‘•’, # common punctuation ‘ff’,‘fi’,‘fl’,‘ffi’,‘ffl’ # ligatures ]
def build_atlases(): atlases = {} # variant -> list[(char, img)] for variant, ttf in FONT_VARIANTS.items(): out = [] for ch in CANDIDATES: img = render_char(ch, ttf) out.append((ch, img)) atlases[variant] = out return atlases
— SSIM match —
def best_match(img: Image.Image, atlases) -> tuple[str, float, str]:
Returns (char, score, variant)
img_n = ImageOps.grayscale(img).resize((128,128), Image.LANCZOS) img_n = ImageOps.autocontrast(img_n) best = (‘’, -1.0, ‘’) import numpy as np candA = np.array(img_n) for variant, entries in atlases.items(): for ch, ref in entries: ref_n = ImageOps.grayscale(ref).resize((128,128), Image.LANCZOS) ref_n = ImageOps.autocontrast(ref_n) candB = np.array(ref_n) score = ssim(candA, candB) if score > best[1]: best = (ch, score, variant) return best
— Putting it together for one TAR batch —
def process_tar(tar_path: str, cache: dict, atlases) -> list[dict]:
cache: perceptual-hash -> mapping
out_runs = [] with tarfile.open(tar_path, ‘r:*’) as tf: glyphs = json.load(tf.extractfile(‘glyphs.json’))
page_data_0_4.json may differ in name; list members to find it
pd_name = next(m.name for m in tf.getmembers() if m.name.startswith(‘page_data_’)) page_data = json.load(tf.extractfile(pd_name))
1. Rasterize + hash all glyphs for this batch
id2hash = {} for gid, meta in glyphs.items(): img = rasterize_svg_path(meta[‘path’]) h = phash_img(img) id2hash[int(gid)] = (h, img)
2. Ensure all hashes are resolved to characters in cache
for h, img in {v[0]: v[1] for v in id2hash.values()}.items(): if h not in cache: ch, score, variant = best_match(img, atlases) cache[h] = { ‘char’: ch, ‘score’: float(score), ‘variant’: variant }
3. Decode text runs
for run in page_data: if run.get(‘type’) != ‘TextRun’: continue decoded = [] for gid in run[‘glyphs’]: h, _ = id2hash[gid] decoded.append(cache[h][‘char’]) run_out = { ‘text’: ‘’.join(decoded), ‘rect’: run.get(‘rect’), ‘fontStyle’: run.get(‘fontStyle’), ‘fontWeight’: run.get(‘fontWeight’), ‘fontSize’: run.get(‘fontSize’), } out_runs.append(run_out) return out_runs
Usage sketch:
atlases = build_atlases()
cache =
for tar in sorted(glob(‘batches/*.tar’)):
runs = process_tar(tar, cache, atlases)
# accumulate runs for layout reconstruction → EPUB/HTML
</details>
## Διάταξη/Επανακατασκευή EPUB — Ευρετικές
- Διαχωρισμός παραγράφων: Εάν το top Y του επόμενου run υπερβαίνει τη baseline της προηγούμενης γραμμής κατά ένα όριο (σχετικό με το μέγεθος γραμματοσειράς), ξεκινήστε νέα παράγραφο.
- Στοίχιση: Ομαδοποιήστε κατά παρόμοιο left X για αριστερά στοιχισμένες παραγράφους· ανιχνεύστε κεντραρισμένες γραμμές από συμμετρικά περιθώρια· ανιχνεύστε δεξιά στοιχισμένες από τα right edges.
- Στυλ: Διατηρήστε πλάγια/έντονα μέσω `fontStyle`/`fontWeight`; διαφοροποιήστε CSS classes ανά `fontSize` buckets για να προσεγγίσετε headings vs body.
- Συνδέσμοι: Εάν τα runs περιέχουν metadata συνδέσμου (π.χ., `positionId`), παράγετε anchors και εσωτερικά hrefs.
## Αντιμετώπιση τεχνικών anti-scraping σε SVG paths
- Use filled paths with `fill-rule: nonzero` and a proper renderer (CairoSVG, resvg). Do not rely on path token normalization.
- Αποφύγετε stroke rendering· εστιάστε σε γεμισμένα solids για να παρακάμψετε hairline artifacts που προκαλούνται από μικρο-σχετικές κινήσεις.
- Κρατήστε σταθερό viewBox ανά render ώστε όμοια σχήματα να rasterize-άρονται συνεπώς σε διάφορα batches.
## Σημειώσεις απόδοσης
- Στην πράξη, τα βιβλία συγκλίνουν σε μερικές εκατοντάδες μοναδικά glyphs (π.χ., ~361 συμπεριλαμβανομένων ligatures). Cache SSIM results by perceptual hash.
- Μετά την αρχική ανακάλυψη, μελλοντικά batches κυρίως επαναχρησιμοποιούν γνωστά hashes· το decoding γίνεται I/O-bound.
- Μέσο SSIM ≈0.95 αποτελεί ισχυρό σήμα· σκεφτείτε να σηματοδοτείτε low-scoring matches για χειροκίνητη ανασκόπηση.
## Γενίκευση σε άλλους viewers
Οποιοδήποτε σύστημα που:
- Επιστρέφει positioned glyph runs με request-scoped numeric IDs
- Στέλνει per-request vector glyphs (SVG paths or subset fonts)
- Περιορίζει σελίδες ανά αίτηση για να αποτρέψει bulk export
…μπορεί να χειριστεί με την ίδια ομαλοποίηση:
- Rasterize per-request shapes → perceptual hash → shape ID
- Atlas of candidate glyphs/ligatures per font variant
- SSIM (or similar perceptual metric) to assign characters
- Reconstruct layout from run rectangles/styles
## Minimal acquisition example (sketch)
Use your browser’s DevTools to capture the exact headers, cookies and tokens used by the reader when requesting `/renderer/render`. Then replicate those from a script or curl. Example outline:
```bash
curl 'https://read.amazon.com/renderer/render' \
-H 'Cookie: session-id=...; at-main=...; sess-at-main=...' \
-H 'x-adp-session: <ADP_SESSION_TOKEN>' \
-H 'authorization: Bearer <RENDERING_TOKEN_FROM_startReading>' \
-H 'User-Agent: <copy from browser>' \
-H 'Accept: application/x-tar' \
--compressed --output batch_000.tar
Προσαρμόστε την παραμετροποίηση (book ASIN, page window, viewport) ώστε να ταιριάζει με τα αιτήματα του αναγνώστη. Αναμένεται όριο 5 σελίδων ανά αίτημα.
Επιτεύξιμα αποτελέσματα
- Συμπτύξτε 100+ randomized alphabets σε έναν ενιαίο glyph space μέσω perceptual hashing
- Χαρτογράφηση 100% των μοναδικών glyphs με μέση SSIM ~0.95 όταν atlases περιλαμβάνουν ligatures και variants
- Ανακατασκευασμένο EPUB/HTML οπτικά αδιάκριτο από το πρωτότυπο
Αναφορές
- Kindle Web DRM: Breaking Randomized SVG Glyph Obfuscation with Raster Hashing + SSIM (Pixelmelt blog)
- CairoSVG – SVG to PNG renderer
- imagehash – Perceptual image hashing (pHash)
- scikit-image – Structural Similarity Index (SSIM)
Tip
Μάθετε & εξασκηθείτε στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
HackTricks

