SVG/Font Glyph Analysis & Web DRM Deobfuscation (Raster Hashing + SSIM)
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
์ด ํ์ด์ง๋ ์์น๊ฐ ์ง์ ๋ glyph runs์ ์์ฒญ๋ณ ๋ฒกํฐ glyph ์ ์(SVG paths)๋ฅผ ํจ๊ป ์ ์กํ๊ณ , ์คํฌ๋ํ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์์ฒญ๋ง๋ค glyph ID๋ฅผ ๋ฌด์์ํํ๋ ์น ๋ฆฌ๋๋ก๋ถํฐ ํ ์คํธ๋ฅผ ๋ณต๊ตฌํ๋ ์ค์ฉ์ ๊ธฐ์ ๋ค์ ๋ฌธ์ํํฉ๋๋ค. ํต์ฌ ์์ด๋์ด๋ ์์ฒญ ๋ฒ์์ ์ซ์ glyph IDs๋ฅผ ๋ฌด์ํ๊ณ ๋์คํฐ ํด์ฑ(raster hashing)์ผ๋ก ์๊ฐ์ ํํ๋ฅผ ์ง๋ฌธํํ ๋ค์, ์ฐธ์กฐ font atlas์ ๋ํด SSIM์ ์ฌ์ฉํด ํํ๋ฅผ ๋ฌธ์๋ก ๋งคํํ๋ ๊ฒ์ ๋๋ค. ์ด ์ํฌํ๋ก์ฐ๋ Kindle Cloud Reader๋ฅผ ๋์ด ์ ์ฌํ ๋ณดํธ๋ฅผ ๊ฐ์ง ๋ชจ๋ ๋ทฐ์ด์ ์ผ๋ฐํ๋ฉ๋๋ค.
๊ฒฝ๊ณ : ์ ๋นํ๊ฒ ์์ ํ ์ฝํ ์ธ ๋ฅผ ๋ฐฑ์ ํ๋ ๊ฒฝ์ฐ ๋ฐ ํด๋น ๋ฒ๋ฅ ๊ณผ ์ฝ๊ด์ ์ค์ํ๋ ๊ฒฝ์ฐ์๋ง ์ด๋ฌํ ๊ธฐ์ ์ ์ฌ์ฉํ์ญ์์ค.
Acquisition (example: Kindle Cloud Reader)
Endpoint observed:
Required materials per session:
- Browser session cookies (normal Amazon login)
- Rendering token from a startReading API call
- Additional ADP session token used by the renderer
Behavior:
- Each request, when sent with browser-equivalent headers and cookies, returns a TAR archive limited to 5 pages.
- For a long book you will need many batches; each batch uses a different randomized mapping of glyph IDs.
Typical TAR contents:
- 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
Example page run structure:
{
"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์ ๋ํ ๋ฉ๋ชจ:
- ๊ฒฝ๋ก์๋ ๋ง์ ๋ฒกํฐ ํ์์ ๋จ์ํ ๊ฒฝ๋ก ์ํ๋ง์ ํผ๋์ํค๋ ๋ง์ดํฌ๋ก ์๋ ์ด๋์ด ํฌํจ๋ ์ ์์(์:
m3,1 m1,6 m-4,-7). - ๋ช ๋ น/์ขํ ์ฐจ๋ถ์ ํ์ง ๋ง๊ณ ๊ฐ๋ ฅํ SVG ์์ง(์: CairoSVG)์ผ๋ก ํญ์ ์ฑ์์ง ์์ ํ ๊ฒฝ๋ก๋ฅผ ๋ ๋๋งํ์ธ์.
Why naรฏve decoding fails
- Per-request randomized glyph substitution: glyph IDโcharacter ๋งคํ์ด ๋ฐฐ์น๋ง๋ค ๋ฌด์์ํ๋จ; ID๋ ์ ์ญ์ ์ผ๋ก ์๋ฏธ๊ฐ ์์.
- Direct SVG coordinate comparison์ ์ทจ์ฝํจ: ๋์ผํ ๋ชจ์์ด๋ผ๋ ์์ฒญ๋ง๋ค ์์น ์ขํ๋ ๋ช ๋ น ์ธ์ฝ๋ฉ์ด ๋ค๋ฅผ ์ ์์.
- OCR on isolated glyphs ์ฑ๋ฅ์ด ๋ฎ์(โ50%): ๊ตฌ๋์ ๊ณผ ์ ์ฌ ๊ธ๋ฆฌํ๋ฅผ ํผ๋ํ๊ณ ligatures๋ฅผ ๋ฌด์ํจ.
Working pipeline: request-agnostic glyph normalization and mapping
- Rasterize per-request SVG glyphs
- ์ ๊ณต๋
path๋ก ๊ธ๋ฆฌํ๋ณ ์ต์ SVG ๋ฌธ์๋ฅผ ๋ง๋ค๊ณ CairoSVG ๋๋ ๊น๋ค๋ก์ด ๊ฒฝ๋ก ์ํ์ค๋ฅผ ์ฒ๋ฆฌํ๋ ๋๋ฑํ ์์ง์ ์ฌ์ฉํด ๊ณ ์ ์บ๋ฒ์ค(์: 512ร512)๋ก ๋ ๋๋งํฉ๋๋ค. - ์ฑ์ฐ๊ธฐ๋ ๊ฒ์ /ํฐ์์ผ๋ก ๋ ๋๋งํ๊ณ , ๋ ๋๋ฌ์ AA์ ๋ฐ๋ฅธ ์ํฐํฉํธ๋ฅผ ์ ๊ฑฐํ๊ธฐ ์ํด strokes๋ ํผํฉ๋๋ค.
- Perceptual hashing for cross-request identity
- ๊ฐ ๊ธ๋ฆฌํ ์ด๋ฏธ์ง์ ๋ํด perceptual hash(์:
imagehash.phash๋ฅผ ํตํ pHash)๋ฅผ ๊ณ์ฐํฉ๋๋ค. - ํด์๋ฅผ ์์ ์ ID๋ก ์ทจ๊ธํ์ธ์: ์์ฒญ ๊ฐ ๋์ผํ ์๊ฐ์ ๋ชจ์์ ๋์ผํ perceptual hash๋ก ์๋ ดํ์ฌ ๋ฌด์์ํ๋ ID๋ฅผ ๋ฌด๋ ฅํํฉ๋๋ค.
- Reference font atlas generation
- ๋์ TTF/OTF ํฐํธ๋ฅผ ๋ค์ด๋ก๋ํฉ๋๋ค(์: Bookerly normal/italic/bold/bold-italic).
- AโZ, aโz, 0โ9, punctuation, ํน์ ๊ธฐํธ(em/en dashes, quotes) ๋ฐ ๋ช
์์ ligatures:
ff,fi,fl,ffi,ffl์ ๋ํ ํ๋ณด๋ฅผ ๋ ๋๋งํฉ๋๋ค. - ํฐํธ ๋ณํ(normal/italic/bold/bold-italic)๋ณ๋ก ๋ณ๋ ์ํ๋ผ์ค๋ฅผ ์ ์งํฉ๋๋ค.
- ligatures์ ๋ํด ๊ธ๋ฆฌํ ์์ค์ ์ถฉ์ค๋๊ฐ ํ์ํ๋ฉด proper text shaper(HarfBuzz)๋ฅผ ์ฌ์ฉํ์ธ์; ๋จ์ํ ligature ๋ฌธ์์ด์ ์ง์ ๋ ๋๋งํ๊ณ shaping ์์ง์ด ์ด๋ฅผ ํด๊ฒฐํ๋ฉด Pillow ImageFont๋ก์ ๊ฐ๋จํ ๋์คํฐํ๋ ์ถฉ๋ถํ ์ ์์ต๋๋ค.
- Visual similarity matching with SSIM
- ๊ฐ ๋ฏธํ์ธ ๊ธ๋ฆฌํ ์ด๋ฏธ์ง์ ๋ํด ๋ชจ๋ ํฐํธ ๋ณํ ์ํ๋ผ์ค์ ํ๋ณด ์ด๋ฏธ์ง๋ค๊ณผ SSIM(Structural Similarity Index)์ ๊ณ์ฐํฉ๋๋ค.
- ์ต๊ณ ์ ์๋ฅผ ๋ฐ์ ๋งค์น์ ๋ฌธ์ ๋ฌธ์์ด์ ํ ๋นํฉ๋๋ค. SSIM์ ํฝ์ ์ ํ ๋น๊ต๋ณด๋ค ์์ ์ํฐ์จ๋ฆฌ์ด์ฑ, ์ค์ผ์ผ, ์ขํ ์ฐจ์ด๋ฅผ ๋ ์ ํก์ํฉ๋๋ค.
- Edge handling and reconstruction
- ๊ธ๋ฆฌํ๊ฐ ligature(๋ค์ค ๋ฌธ์)๋ก ๋งคํ๋๋ฉด ๋์ฝ๋ฉ ์ ํ์ฅํฉ๋๋ค.
- ๋ฐ ์ฌ๊ฐํ(top/left/right/bottom)์ ์ฌ์ฉํด ๋ฌธ๋จ ๊ตฌ๋ถ(Y ๋ธํ), ์ ๋ ฌ(X ํจํด), ์คํ์ผ ๋ฐ ํฌ๊ธฐ๋ฅผ ์ถ๋ก ํฉ๋๋ค.
fontStyle,fontWeight,fontSize๋ฐ ๋ด๋ถ ๋งํฌ๋ฅผ ๋ณด์กดํ์ฌ HTML/EPUB๋ก ์ง๋ ฌํํฉ๋๋ค.
Implementation tips
- ํด์ฑ ๋ฐ SSIM ์ ์ ๋ชจ๋ ์ด๋ฏธ์ง๋ฅผ ๋์ผํ ํฌ๊ธฐ์ ๊ทธ๋ ์ด์ค์ผ์ผ๋ก ์ ๊ทํํ์ธ์.
- ํผ์ ์ถ์ผ ํด์๋ก ์บ์ํ์ฌ ๋ฐฐ์น ๊ฐ ๋ฐ๋ณต ๊ธ๋ฆฌํ์ ๋ํด SSIM ์ฌ๊ณ์ฐ์ ํผํ์ธ์.
- ๋ ๋์ ์๋ณ์ ์ํด ๊ณ ํ์ง ๋์คํฐ ํฌ๊ธฐ(์: 256โ512 px)๋ฅผ ์ฌ์ฉํ๊ณ , SSIM ๊ฐ์์ ์ํด ํ์ ์ ์ถ์ํ์ธ์.
- Pillow๋ก TTF ํ๋ณด๋ฅผ ๋ ๋๋งํ๋ ๊ฒฝ์ฐ ๋์ผํ ์บ๋ฒ์ค ํฌ๊ธฐ๋ฅผ ์ค์ ํ๊ณ ๊ธ๋ฆฌํ๋ฅผ ๊ฐ์ด๋ฐ์ ๋ฐฐ์นํ๋ฉฐ, ascender/descender๊ฐ ์๋ฆฌ์ง ์๋๋ก ํจ๋ฉํ์ธ์.
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>
## Layout/EPUB reconstruction heuristics
- Paragraph breaks: ๋ค์ run์ top Y๊ฐ ์ด์ ์ค์ baseline์ ํฐํธ ํฌ๊ธฐ์ ์๋์ ์ธ ์๊ณ๊ฐ ์ด์์ผ๋ก ์ด๊ณผํ๋ฉด ์ ๋ฌธ๋จ์ ์์ํฉ๋๋ค.
- Alignment: ์ผ์ชฝ ์ ๋ ฌ ๋ฌธ๋จ์ ์ ์ฌํ left X๋ก ๊ทธ๋ฃนํํฉ๋๋ค; ๊ฐ์ด๋ฐ ์ ๋ ฌ์ ๋์นญ ์ฌ๋ฐฑ์ผ๋ก ๊ฐ์งํ๊ณ ; ์ค๋ฅธ์ชฝ ์ ๋ ฌ์ ์ค๋ฅธ์ชฝ ๊ฐ์ฅ์๋ฆฌ๋ก ๊ฐ์งํฉ๋๋ค.
- Styling: ๊ธฐ์ธ์/๊ตต๊ฒ๋ `fontStyle`/`fontWeight`๋ก ๋ณด์กดํฉ๋๋ค; ์ ๋ชฉ๊ณผ ๋ณธ๋ฌธ์ ๊ทผ์ฌํํ๊ธฐ ์ํด `fontSize` ๋ฒํท๋ณ๋ก CSS ํด๋์ค๋ฅผ ๋ฌ๋ฆฌํฉ๋๋ค.
- Links: runs์ ๋งํฌ ๋ฉํ๋ฐ์ดํฐ(์: `positionId`)๊ฐ ํฌํจ๋์ด ์์ผ๋ฉด ์ต์ปค์ ๋ด๋ถ href๋ฅผ ์์ฑํฉ๋๋ค.
## Mitigating SVG anti-scraping path tricks
- Use filled paths with `fill-rule: nonzero` and a proper renderer (CairoSVG, resvg). ๊ฒฝ๋ก ํ ํฐ ์ ๊ทํ์ ์์กดํ์ง ๋ง์ธ์.
- Avoid stroke rendering; ์ฑ์์ง ์๋ฆฌ๋์ ์ง์คํ์ฌ ๋ฏธ์ธํ ์๋ ์ด๋์ผ๋ก ๋ฐ์ํ๋ ํค์ด๋ผ์ธ ์ํฐํฉํธ๋ฅผ ํํผํ์ธ์.
- ๋ ๋๋ง๋ค ์์ ์ ์ธ `viewBox`๋ฅผ ์ ์งํ์ฌ ๋์ผํ ๋ํ์ด ๋ฐฐ์น ๊ฐ์ ์ผ๊ด๋๊ฒ ๋์คํฐํ๋๋๋ก ํฉ๋๋ค.
## Performance notes
- ์ค๋ฌด์์๋ ์ฑ
์ด ์๋ฐฑ ๊ฐ์ ๊ณ ์ ๊ธ๋ฆฌํ(์: ํฉ์ ํฌํจ ์ฝ 361๊ฐ)๋ก ์๋ ดํฉ๋๋ค. SSIM ๊ฒฐ๊ณผ๋ฅผ perceptual hash๋ก ์บ์ํ์ธ์.
- ์ด๊ธฐ ๋ฐ๊ฒฌ ์ดํ ์ดํ ๋ฐฐ์น๋ค์ ์ฃผ๋ก ์๋ ค์ง ํด์๋ฅผ ์ฌ์ฌ์ฉํ๋ฏ๋ก ๋์ฝ๋ฉ์ด I/O-bound๊ฐ ๋ฉ๋๋ค.
- ํ๊ท SSIM โ0.95๋ ๊ฐํ ์ ํธ์
๋๋ค; ์ ์๊ฐ ๋ฎ์ ๋งค์น๋ ์๋ ๊ฒํ ๋ฅผ ์ํด ํ๋๊ทธํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
## Generalization to other viewers
๋ค์์ ์ ๊ณตํ๋ ๋ชจ๋ ์์คํ
:
- ์์ฒญ ๋ฒ์์ ์ซ์ ID์ ํจ๊ป ์์น ์ง์ ๋ glyph runs๋ฅผ ๋ฐํ
- ์์ฒญ๋ณ ๋ฒกํฐ ๊ธ๋ฆฌํ(SVG paths ๋๋ subset fonts)๋ฅผ ์ ์ก
- ๋๋ ์ถ์ถ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์์ฒญ๋น ํ์ด์ง ์๋ฅผ ์ ํ
โฆ๊ฐ์ ์ ๊ทํ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค:
- ์์ฒญ๋ณ ๋ํ ๋์คํฐํ โ perceptual hash โ shape ID
- ๊ธ๊ผด ๋ณํ๋ณ ํ๋ณด ๊ธ๋ฆฌํ/ํฉ์ ์ํ๋ผ์ค
- ๋ฌธ์๋ฅผ ํ ๋นํ๊ธฐ ์ํ SSIM(๋๋ ์ ์ฌํ perceptual metric)
- run ์ฌ๊ฐํ/์คํ์ผ๋ก๋ถํฐ ๋ ์ด์์ ์ฌ๊ตฌ์ฑ
## Minimal acquisition example (sketch)
๋ธ๋ผ์ฐ์ ์ DevTools๋ฅผ ์ฌ์ฉํ์ฌ reader๊ฐ `/renderer/render`๋ฅผ ์์ฒญํ ๋ ์ฌ์ฉ๋๋ ์ ํํ ํค๋, ์ฟ ํค ๋ฐ ํ ํฐ์ ์บก์ฒํ์ธ์. ๊ทธ๋ฐ ๋ค์ ์คํฌ๋ฆฝํธ๋ curl์์ ์ด๋ฅผ ๋ณต์ ํ์ธ์. ์์ ๊ฐ์:
```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
๋ ์์ ์์ฒญ์ ๋ง๊ฒ ํ๋ผ๋ฏธํฐ(์ฑ ASIN, ํ์ด์ง ์๋์ฐ, viewport)๋ฅผ ์กฐ์ ํ์ธ์. ์์ฒญ๋น ์ต๋ 5ํ์ด์ง ์ ํ์ด ์ ์ฉ๋ฉ๋๋ค.
๋ฌ์ฑ ๊ฐ๋ฅํ ๊ฒฐ๊ณผ
- perceptual hashing์ ํตํด 100๊ฐ ์ด์์ ๋ฌด์์ํ๋ ์ํ๋ฒณ์ ๋จ์ผ ๊ธ๋ฆฌํ ๊ณต๊ฐ์ผ๋ก ์ถ์
- ์ํ๋ผ์ค๊ฐ ํฉ์(ligatures)์ ๋ณํ(variants)์ ํฌํจํ ๋ ๊ณ ์ ๊ธ๋ฆฌํ๋ฅผ ํ๊ท SSIM ~0.95๋ก 100% ๋งคํ
- ์ฌ๊ตฌ์ฑ๋ EPUB/HTML์ด ์๋ณธ๊ณผ ์๊ฐ์ ์ผ๋ก ๊ตฌ๋ณ ๋ถ๊ฐ
References
- 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 ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


