Chromium 148부터 Math.tanh로 기반 OS를 식별할 수 있음
3 hours ago
1
Chrome 148부터 V8의 Math.tanh가 내장 fdlibm 대신 호스트의 std::tanh를 호출하면서, 같은 입력도 Linux의 glibc·macOS의 libsystem_m·Windows의 UCRT에서 서로 다른 마지막 비트 를 반환함
Math.tanh(0.8)은 Linux에서 0.6640367702678491, macOS에서 0.664036770267849, Windows에서 0.6640367702678489가 되어 한 번의 호출로 세 OS를 구분 할 수 있으며, User-Agent가 주장하는 OS와 결과가 다르면 위장이 드러남
엔진마다 누출 경로가 달라 V8의 Math.*에서는 tanh만 호스트 수학 라이브러리를 사용하지만, Blink의 CSS 삼각함수 전체 와 Web Audio 일부 연산도 OS별 라이브러리를 거침
값을 임의로 흔들면 실제 OS 어느 쪽과도 일치하지 않고 결정성까지 깨지므로, 대상 라이브러리의 계수·테이블·범위 축소·FMA 동작을 비트 단위로 재현 하거나 원본 UCRT 코드를 직접 매핑해야 함
Scrapfly는 릴리스마다 871,000개 입력을 실제 Mac과 Chrome에 대조해 Math.tanh와 CSS 삼각함수 7개의 비트 일치를 검증하며, 정확도뿐 아니라 아키텍처 차이와 실행 시간 도 실제 브라우저 수준으로 맞춤
Math.tanh가 드러내는 OS
Math.tanh(0.8)의 결과는 호스트 수학 라이브러리에 따라 달라짐
Linux Chrome의 glibc: 0.6640367702678491
macOS Chrome의 libsystem_m: 0.664036770267849
Windows Chrome의 UCRT: 0.6640367702678489
Apple과 glibc는 전체 입력의 약 4분의 1 에서 대체로 1 ULP 차이를 보이며, Windows UCRT는 두 라이브러리와 몇 퍼센트의 입력에서 달라짐
ULP(unit in the last place)는 특정 크기에서 표현 가능한 연속 부동소수점 수 사이의 간격이며, 1 ULP는 double이 나타낼 수 있는 최소 차이임
실제 Chrome 150을 Linux, Apple Silicon 기반 macOS 26, Windows 11에서 DevTools Protocol로 측정하면 입력에 따라 분류력이 달라짐
tanh(0.5)는 세 OS 모두 0.46211715726000974로 같아 탐지에 쓸 수 없음
tanh(0.7)은 Linux만 1 ULP 차이남
tanh(0.8)은 세 OS가 모두 다르며 전체 범위는 2 ULP임
tanh(0.9)는 Windows만 1 ULP 차이남
약 4분의 3의 입력에서는 세 OS가 같은 결과를 내지만, 적절한 입력 하나면 OS별 서명 을 얻을 수 있음
macOS를 주장하면서 Linux의 수학 비트를 반환하면 Math.tanh 결과가 User-Agent와 모순됨
Chrome 148에서 생긴 변화
Chrome 147까지 V8은 이식 가능한 수학 구현인 fdlibm 포트를 내장해 Math.tanh를 계산했으므로 모든 OS에서 같은 비트를 반환함
V8 커밋 c1486295ae5 은 내장 구현을 플랫폼의 std::tanh로 교체함
이 변경은 V8 14.8.57과 Chrome 148에 처음 포함됨
Chrome 148·149·150은 호스트 libm의 차이를 노출하지만, Chrome 147 이하는 이 경로로 OS를 누출하지 않음
IEEE 754 는 double의 저장 방식을 정의하지만 sin, cos, tanh, exp 같은 초월함수가 반드시 올바르게 반올림되도록 요구하지 않음
각 OS의 수학 라이브러리(libm) 는 성능과 ULP 오차를 절충하며 서로 다른 최소최대 근사 다항식 계수, 조회 테이블, 범위 축소 상수를 사용함
Linux는 glibc
macOS는 Apple libsystem_m
Windows는 UCRT의 ucrtbase.dll
탐지기는 수학 연산 자체를 분석할 필요 없이 실제 Chrome의 입력별 결과표와 값을 비교할 수 있음
재현을 어렵게 만드는 네 가지 함정
V8의 일부 함수만 누출됨
V8은 대부분의 수학 구현을 정적으로 링크하므로 OS와 무관하게 같은 결과를 냄
Math.exp, Math.pow, Math.atan 등은 내장 llvm-libc 구현을 사용함
Math.sin과 Math.cos는 glibc에서 유래한 내장 dbl-64 루틴을 사용함
Chrome 148 이후 플랫폼 std::tanh를 쓰는 Math.tanh만 Math.* 중에서 OS를 누출함
누출하지 않는 함수까지 대상 OS처럼 위조하면 V8의 실제 호출 구조와 어긋나며, tanh만 다르다는 비대칭 자체도 검사할 수 있음
JavaScript와 CSS가 서로 다른 경로를 사용함
CSS의 sin(), cos(), atan2()는 JavaScript의 Math.sin과 코드를 공유하지 않음
Blink 레이아웃 엔진은 각도를 도 단위로 축소한 뒤, 축소된 값에 플랫폼 std::sin 등을 호출함
직접 라디안 입력을 계산한 결과와 달라지며, CSS 삼각함수 7개 전체 가 호스트 libm을 통해 OS를 누출함
비트 단위 재현에는 최종 수학 함수뿐 아니라 도 단위 범위 축소와 라디안·도 변환 과정도 포함해야 함
macOS 안에도 서로 다른 두 라이브러리가 있음
Apple Silicon에는 스칼라 libsystem_m과 Accelerate 의 벡터 루틴인 vvsin, vvtanh가 함께 있으며 두 구현은 같지 않음
100만 개 입력에서 함수에 따라 10~89% 의 결과가 달랐음
cos(0)은 스칼라 구현에서 정확히 1.0임
Accelerate에서는 0.9999999999999999를 반환함
실제 Mac의 Chrome을 디버깅 프로토콜로 측정해 호출 지점별 라이브러리를 구분함
Math.tanh, CSS 삼각함수, 오디오 압축기의 샘플별 초월함수는 스칼라 libsystem_m을 사용함
Mac의 Web Audio DSP, FFT, 벡터 수학, 바이쿼드 필터는 Accelerate를 사용함
관련 Chromium 경로에는 fft_frame_mac.cc, vector_math_mac.h, biquad.cc와 BUILDFLAG(IS_MAC)가 있음
호출 지점에 맞지 않는 Apple 라이브러리를 선택하면 대부분의 입력에서 1 ULP가 어긋날 수 있음
CPU 아키텍처도 결과에 개입함
ARM과 x86은 융합 곱셈-덧셈(FMA) 및 NaN 부호 전파에서 차이를 보임
수학적 절차가 맞더라도 컴파일러가 한 아키텍처에서만 곱셈과 덧셈을 융합하면 결과 비트가 달라짐
엔진과 기능별 누출 경로
JavaScript의 V8 Math.*는 거의 모두 내장 구현을 사용하며, 호스트 libm으로 연결되는 지점은 Math.tanh 하나임
sin, cos, tan, asin, acos, atan, atan2, exp, log, log2, log10, pow는 V8 내장 구현을 사용함
sqrt, abs, 사칙연산은 하드웨어 연산임
CSS calc()의 수학 함수는 Blink가 플랫폼 라이브러리를 직접 호출함
sin, cos, tan, asin, acos, atan, atan2, exp, log, log2, log10, pow가 호스트 libm 을 사용함
CSS에는 대응하는 tanh 경로가 없음
Web Audio는 호출 지점에 따라 여러 구현을 섞어 사용함
Mac의 오실레이터 FFT, 벡터 덧셈·곱셈·스케일, FFT는 Accelerate의 vDSP를 사용함
DynamicsCompressor의 sin, exp, log10f, powf 등 샘플별 초월함수는 스칼라 libsystem_m을 사용함
하나의 오디오 그래프가 V8 내장 수학, 스칼라 라이브러리, Accelerate 등 세 가지 라이브러리 에 걸칠 수 있음
WebAssembly에는 초월함수 명령어가 없음
sin 등의 결과는 모듈에 포함된 libm에 따라 결정됨
f64.sqrt, f64.mul 같은 산술은 하드웨어에서 실행되므로 OS별로 같음
남는 지문 축은 ARM과 x86 사이의 NaN 정규화 및 일부 SIMD 반올림 차이임
탐지 신호는 Math.tanh, 모든 CSS 삼각함수, Web Audio에 집중됨
Web Audio의 Accelerate FFT는 CPU 아키텍처를 드러냄
압축기의 스칼라 libsystem_m은 OS를 드러냄
값을 흔드는 대신 정확히 재현하기
노이즈가 실패하는 이유
결과에 노이즈를 추가하면 기준표에 있는 어느 실제 OS 값과도 일치하지 않을 수 있음
호출마다 무작위 값이 달라지면 결정성 이 깨지며, 이 현상 자체가 별도의 탐지 신호가 됨
목표는 비슷한 값이 아니라 주장한 OS가 반환하는 값과 비트 단위로 같은 결과임
대상 알고리듬의 모든 요소 복원
대상 libm에서 최소최대 근사 계수, 지수 테이블, 범위 축소 상수를 복구해 이식 가능한 C 코드로 옮김
대상 라이브러리가 잘못된 방향으로 반올림하는 입력까지 그대로 일치시켜야 함
Apple sin 재현은 libsystem_m에서 추출한 계수의 정확한 비트 패턴 과 명시적인 fma() 호출을 사용함
계수를 10진수로 옮기면 전사 과정에서 다시 반올림될 수 있으므로 16진 부동소수점 값으로 보존함
Apple이 융합하는 각 곱셈-덧셈을 코드에서도 명시적으로 융합함
FMA를 결정적으로 고정
-ffp-contract=off로 컴파일해 컴파일러가 임의로 FMA를 추가하거나 제거하지 못하게 함
코드에 명시한 fma()만 Apple과 같은 위치에서 실행되므로 ARM을 모방하면서 x86 서버에서 실행해도 같은 비트를 얻을 수 있음
하드웨어 FMA와 올바르게 반올림되는 소프트웨어 FMA는 같은 비트를 반환함
Windows UCRT 원본 코드 사용
Windows UCRT는 Linux 서버와 같은 x86-64 ISA를 사용하고 위치 독립적이므로, 실제 ucrtbase.dll을 런타임 메모리에 매핑하고 수학 함수 내보내기(export)를 직접 호출할 수 있음
원본 코드를 실행하므로 별도의 수학 알고리듬 역공학 없이 실제 UCRT 비트 를 얻을 수 있음
Linux의 System V ABI와 Windows x64 ABI 차이를 처리해야 함
Windows x64에서는 피호출자가 반환 주소 위의 32바이트 shadow space를 사용함
피호출자 보존 레지스터 집합도 System V와 다름
함수 포인터를 ms_abi로 선언하지 않으면 shadow-space 쓰기가 clang 스택 프레임을 손상시키고 간접 호출이 잘못된 주소로 이동할 수 있음
매핑한 DLL 코드는 CFI에 등록된 간접 호출 대상이 아님
프로덕션의 -fsanitize=cfi-icall이 호출마다 #UD 트랩과 SIGILL을 일으킬 수 있음
함수 포인터를 호출하는 래퍼에는 clang::no_sanitize("cfi-icall")가 필요함
UCRT 수학 함수는 시작 부분의 mov eax, [rip+disp32]로 CPU 디스패치 플래그를 읽어 스칼라 또는 FMA/AVX2 경로를 선택함
새로 매핑한 DLL에서는 플래그가 0이라 느린 스칼라 경로를 선택함
이 경로의 결과는 최신 Windows 시스템의 결과 비트와 다름
tanh 프롤로그에서 플래그 주소를 찾아 첫 호출 전에 FMA 경로로 강제해야 실제 Windows와 비트 단위로 일치함
패치 위치와 성능 제약
엔진이 libm을 호출하는 단일 병목 지점 을 훅하고, 브라우저가 주장하는 OS에 따라 경로를 선택함
Linux를 주장하면 glibc를 유지함
macOS를 주장하면 Apple 재현 구현을 사용함
결과가 정확해도 실행 시간이 실제 브라우저와 다르면 탐지할 수 있음
첫 빌드는 기본 x86 기준선이 하드웨어 FMA보다 오래돼 모든 fma()를 소프트웨어 호출로 낮췄으며, 네이티브보다 2.5~6배 느렸음
Math.tanh와 Math.sin 반복문의 시간 비율을 비교하면 실제 브라우저에는 없는 성능 패턴이 드러날 수 있음
하드웨어 FMA를 활성화하자 각 융합 연산이 단일 명령어가 되어 약 6배 빨라졌으며, glibc보다 빠르면서도 결과 비트는 동일했음
871,000개 입력으로 검증
검증 하네스는 릴리스마다 871,000개 입력 을 모든 분기와 정의역에 걸쳐 실행함
조밀한 입력 격자
구간 경계
비정규수
부호 있는 0
무한대
NaN
두 종류의 실제 환경을 기준값으로 사용함
실제 Mac이 모든 입력에서 스칼라와 Accelerate 결과를 각각 계산해 두 구현이 갈리는 지점을 확인함
실제 Mac의 Chrome을 디버깅 프로토콜로 구동해 Math.tanh와 모든 CSS 삼각함수의 전체 정밀도 결과를 수집함
Math.tanh와 CSS의 sin, cos, tan, asin, acos, atan, atan2가 실제 Mac Chrome과 비트 단위로 일치 함
재현 구현이 배포 바이너리 안의 실제 기계어와 동일하게 동작하는지도 검증함
정의역 경계의 브라우저 후처리까지 맞춰야 함
실제 Mac에서 CSS asin(2)는 정의역 밖이라 NaN이 되고 CSS가 NaN을 0으로 제한하므로 최종값은 0임
단순한 재현 구현은 이를 90도로 잘못 반환할 수 있음
브라우저 위장에서 수학이 중요한 이유
수학 결과는 결정적이고 저렴하게 검사 할 수 있지만, 벤더 libm 내부와 엔진별 호출 경로를 알아야 정확히 위장할 수 있음
실제 브라우저와 일치하려면 V8·Blink·Web Audio가 호출 지점마다 어떤 수학 라이브러리를 선택하는지 파악하고, 마지막 비트와 아키텍처별 동작 및 실행 시간까지 맞춰야 함
Scrapfly의 Scrapium 은 macOS로 표시하도록 요청받으면 코사인의 반올림 비트까지 실제 macOS 트래픽과 맞추도록 구성됨
Homepage
Tech blog
Chromium 148부터 Math.tanh로 기반 OS를 식별할 수 있음
🔉 볼륨 줄이기
🔊 볼륨 키우기
🔇 음소거
⏭️ 다음 곡