Chromium 148부터 Math.tanh로 기반 OS를 식별할 수 있음
(scrapfly.dev)- 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
- Linux Chrome의 glibc:
- Apple과 glibc는 전체 입력의 약 4분의 1에서 대체로 1 ULP 차이를 보이며, Windows UCRT는 두 라이브러리와 몇 퍼센트의 입력에서 달라짐
- ULP(unit in the last place)는 특정 크기에서 표현 가능한 연속 부동소수점 수 사이의 간격이며, 1 ULP는
double이 나타낼 수 있는 최소 차이임
- ULP(unit in the last place)는 특정 크기에서 표현 가능한 연속 부동소수점 수 사이의 간격이며, 1 ULP는
- 실제 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를 누출함 - 비트 단위 재현에는 최종 수학 함수뿐 아니라 도 단위 범위 축소와 라디안·도 변환 과정도 포함해야 함
- CSS의
-
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가 어긋날 수 있음
- Apple Silicon에는 스칼라
-
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 등 세 가지 라이브러리에 걸칠 수 있음
- Mac의 오실레이터 FFT, 벡터 덧셈·곱셈·스케일, FFT는 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도로 잘못 반환할 수 있음
- 실제 Mac에서 CSS
브라우저 위장에서 수학이 중요한 이유
- 수학 결과는 결정적이고 저렴하게 검사할 수 있지만, 벤더
libm내부와 엔진별 호출 경로를 알아야 정확히 위장할 수 있음 - 실제 브라우저와 일치하려면 V8·Blink·Web Audio가 호출 지점마다 어떤 수학 라이브러리를 선택하는지 파악하고, 마지막 비트와 아키텍처별 동작 및 실행 시간까지 맞춰야 함
- Scrapfly의 Scrapium은 macOS로 표시하도록 요청받으면 코사인의 반올림 비트까지 실제 macOS 트래픽과 맞추도록 구성됨
댓글과 토론
Hacker News 의견들
-
오른쪽 입력으로
tanh를 한 번 호출한 결과가 운영체제별 서명이 된다는 설명은 브라우저 버전 범위를 식별할 가능성을 놓쳤음
대부분은 User-Agent의 운영체제를 위조하지 않으며, 핑거프린팅은 운영체제 자체보다 준고유한 특성 조합에 관심을 둠. 발견은 흥미롭지만 글이 지나치게 LLM으로 작성돼 신뢰감을 떨어뜨림- 이 글을 만든 회사는 실제로 Linux VM의 봇을 Windows나 macOS 물리 장비처럼 위장하려 함
그래야 봇 탐지를 더 쉽게 통과하고 다른 웹사이트에서 수집한 데이터를 고객에게 판매할 수 있음 - 지금 이 방법만으로는 Chromium 148 이상이라고만 판별할 수 있지만, 각 버전에서 추가된 V8·Blink 기능을 JavaScript나 CSS로 검사하면 약 120 버전 이후부터 주 버전을 확실히 특정할 수 있음
LLM으로 작성했다는 사실은 글과 블로그에 공개했으며 숨기거나 사람인 척한 것이 아님. 시간이 부족해 그렇지 않았다면 글을 아예 게시하지 못했을 것이고, 이 선택에 책임질 생각임 - 버전 범위를 식별할 수는 있지만 그런 수단은 이미 셀 수 없이 많음
브라우저는 계속 기능을 추가하고 버그를 수정하며, 그중 대부분을 JavaScript로 감지할 수 있음 - 내용이 사실이라면 누가 썼는지는 상관없으며, LLM의 핵심 논지도 타당함
- 이 글을 만든 회사는 실제로 Linux VM의 봇을 Windows나 macOS 물리 장비처럼 위장하려 함
-
모든 핑거프린팅 기법을 AI로 분석해 공개하고, 논란 끝에 브라우저가 이를 막도록 유도하면 자사 스크래핑 사업이 더 많은 돈을 벌 수 있으니 영리한 전략임
이런 회사들이 없었다면 브라우저 핑거프린팅이 지금처럼 만연하지 않았고 인터넷도 더 나았을 것임. 차라리 fingerprint.js처럼 이해관계가 명확한 반대편의 글을 선호함- 스크래퍼가 있든 없든 인간을 추적하려면 핑거프린팅이 필요하며 결국 사용될 것이므로 동의하기 어려움
-
정확한 반올림 초월함수를 추진해야 할 이유가 하나 더 생김
최근에는 이 문제가 사실상 해결됐다는 것을 알게 됨. https://arith2026.org/program.html의 두 번째 기조연설 참고- 정확히 반올림되는
libm함수는 훌륭하지만, 과거 glibc의pow처럼 최악 성능이 끔찍해서는 안 됨
반올림 경계에 가까울 때 사용하는 고정밀 대체 경로를 직접 SLP 벡터화해 최악 성능을 개선해 볼 수 있지만, 이미 대부분의 용도에는 충분함. JavaScript 엔진이 ECMAScript 명세에서 권장하는fdlibm을 계속 쓰지 않는 점은 의외이며,Math.tanh가 JavaScript의 병목 경로라면 상당히 특이한 코드임 - 왜 고정 정밀도와 정수 연산이 더 널리 쓰이지 않는지 이해하기 어려움
공학에서는 훨씬 단순한 하드웨어에서 동작하고 오차를 수학적으로 쉽게 모델링할 수 있어 고정소수점을 자주 사용했음. IEEE 754 부동소수점은 이론적으로도 미심쩍으며, 정밀도 손실에서는 가수보다 작은 정수, 즉 24비트 미만 정수가 32비트 부동소수점보다 나을 때도 있음 - 해마다 새 도메인을 등록하고 영구적으로 갱신하는 방식인지 놀라움
- 정확히 반올림되는
-
이 기법이 https://coveryourtracks.eff.org/에 추가돼 내 수학 함수 결과가 더 큰 모집단에서 얼마나 고유한지 확인할 수 있으면 좋겠음
- 이 회사는 Chromium의 C++ 파일 550개 이상에서 신호 4,000개 이상을 패치했다고 주장함
사실인지는 모르겠지만 coveryourtracks.eff.org가 쓰는 신호는 25개 정도 아닌가 싶음
- 이 회사는 Chromium의 C++ 파일 550개 이상에서 신호 4,000개 이상을 패치했다고 주장함
-
글을 Claude가 작성한 티가 남
- 글 상단의 AI 요약 링크는 선택한 AI 제공자에게 기사 요약뿐 아니라 제품 광고까지 요청한다는 점이 황당함
Claude 링크를 누르면summarize+this+article+and+explain+how+scrapfly+helps+me+scrape+any+website+at+scale+and+bypass+anti-bot+systems+for+my+use+case:+[https://scrapfly.dev/posts/browser-math-os-fingerprint/](<https://scrapfly.dev/posts/browser-math-os-fingerprint/>)라는 프롬프트가 전달됨 - 제목의 발견은 흥미롭지만 나머지는 사실상 Claude가 작성한 내용임
- 최근 몇 달간 HN 활동이 뜸했는데, 커뮤니티가 콘텐츠를 저질로 낙인찍고 LLM 사용을 탐지하는 데 편집증적으로 집착하는 듯함
- 글 상단의 AI 요약 링크는 선택한 AI 제공자에게 기사 요약뿐 아니라 제품 광고까지 요청한다는 점이 황당함
-
Tor Browser와 Mullvad Browser도 결국 운영체제 은폐를 포기했지만, 어쩌면 그러지 말았어야 함
그만큼 핑거프린팅 경로가 지나치게 많아 보임- 운영체제 숨기기가 가능한지도 불분명하므로 올바른 결정이었다고 봄
브라우저 안팎에서 운영체제별 동작 차이가 너무 많이 발생해 모두 처리하기 어려움. 캔버스 추출을 차단하거나 잡음을 넣어도 렌더링 차이가 노출될 수 있으며, Tor Browser 개발자도 완전히 다른 운영체제는커녕 X11과 Wayland의 차이조차 숨길 수 없다고 확인했음. https://forum.torproject.org/t/linux-is-it-alright-to-run-th... - Tor Browser는
navigator.platform조차 수정하지 않으므로 Windows가 아닌 환경을 알아내기가 매우 쉬움
- 운영체제 숨기기가 가능한지도 불분명하므로 올바른 결정이었다고 봄
-
선호하는 JavaScript 주입 플러그인으로 다음 코드를 넣으면 됨:
let oldTanh = Math.tanh; Math.tanh = x => oldTanh(x) + Math.random()/10000000;- 더 간결하게
Math.tanh = Math.random;을 선호함 - 글에서 이미 다루고 있으니 “No noise”를 검색하면 됨
- 여러 봇 방지 업체가 이 교체를 탐지해 핑거프린팅 신호로 활용할 것임
- 이제 정상값 대신 핑거프린트를 숨기는 사용자로 드러나 오히려 더 식별하기 쉬워질 수 있음
- 더 간결하게
-
최신 glibc는 CORE-MATH의 정확히 반올림되는
tanh를 사용하므로 글에 인용된 값과 다른 결과를 반환함
다른 초월함수도 합리적인 성능으로 정확한 반올림을 구현할 수 있는지는 아직 불분명해 각 함수가 저마다 고유한 핑거프린트를 남김 -
Chrome은 실행 코드만 수백 MB라 사용자 공간 라이브러리의 절반쯤은 정적 링크했을 줄 알았음
또한tanh는 함수 호출이 아니라 JavaScript JIT가 CPU 명령으로 내보내는 내장 연산이라고 생각했는데, 수학 연산을 위해dlsym()함수로 분기해야 한다는 점이 이상함. CPU 명령 자체도 핑거프린팅될 수는 있음- x87 FPU는 초월함수를 마이크로코드로 구현했지만 대부분의 명령어 집합은 이를 제공하지 않음
마이크로코드는 분기 예측 같은 이점을 얻지 못해 실제로 소프트웨어 구현보다 느림 - 기억하기로 Chrome은 JIT되지 않은 모드에서 NaN 값의 사용되지 않는 비트를 보존하는 유일한 브라우저이며, 코드가 JIT되면 그 비트가 0으로 바뀜
- x87 FPU는 초월함수를 마이크로코드로 구현했지만 대부분의 명령어 집합은 이를 제공하지 않음
-
이 싸움에서 이길 수 있는지 의문임
충분히 많은 함수를 실행하면 실행 시간 비율과 반올림 결과를 조합해 운영체제와 정확한 기종은 물론, 같은 장비에서 실행 중인 다른 작업까지 추정할 수 있을 듯함. 완전히 막기보다는 조금 어렵게 만드는 정도만 가능해 보임
결국 사회와 법이 따라잡아야 함. 문 잠금장치가 침입을 완전히 막지 못해도 사회적 비난과 형사 처벌이 보완하듯, 이런 방식의 개인 추적을 불법화하고 그 이익을 사용하는 회사와 취업자를 사회적으로 배척해야 함- 사이버 공간에서는 불법이거나 불법이어야 할 행위를 하는 이들이 집행 불가능한 관할권에 있는 경우가 많음
러시아·미얀마·북한 같은 곳에서는 법치가 작동하지 않고, 외국인을 속이는 범죄자를 현지 당국이 적극적으로 보호하기도 하므로 문 잠금장치 비유가 성립하지 않음
- 사이버 공간에서는 불법이거나 불법이어야 할 행위를 하는 이들이 집행 불가능한 관할권에 있는 경우가 많음