네트워크 위치를 바꿔치기하는 프록시 스푸퍼gs-loc.apple.com에 보내 좌표를 받아오는데, wloc는 그 protobuf 응답을 실시간으로 리라이트한다. 온라인 지도에서 점 하나 찍으면 끝.
(저장소: Yu9191/wloc · 주 언어 JavaScript · ★ 4.3k · fork 807 · 라이선스 없음 · 최신 커밋 2026-06-27 · TrendShift 이번 주 Weekly #18)
w(WiFi 기반) + loc(location) = "WiFi 네트워크 위치를 바꾸는 도구"wloc는 Apple의 네트워크 위치 서비스(WLOC)가 반환하는 좌표를 iOS 프록시 앱의 MITM·스크립트로 가로채, protobuf 응답 안의 위경도를 실시간으로 바꾸는 "탈옥·PC 불필요"의 순수 프록시 방식 가상 위치 도구다. 사용자는 웹 지도에서 원하는 지점을 클릭하고 "기기에 저장"만 누르면, 그 뒤로 아이폰이 네트워크로 위치를 조회할 때마다 조작된 좌표를 진짜 위치로 믿게 된다.
여기서 반드시 구분할 것 하나. wloc는 GPS 신호를 조작하는 게 아니라, Apple 서버가 돌려주는 "답"을 바꾼다. 아이폰의 위치는 GPS 위성 + WiFi/기지국 네트워크 측위 + 블루투스 비콘 등을 종합해 결정되는데, wloc는 그중 네트워크 측위(WLOC) 경로 하나만 건드린다. 그래서 GPS 신호가 강한 야외에서는 덜 먹히고, WiFi 측위에 의존하는 실내에서 가장 잘 통한다(§2·§7에서 상세).
아이폰이 낯선 곳에서 "제가 지금 어디 있나요?"라고 Apple 안내데스크(gs-loc.apple.com)에 물어보는 손님이라면, wloc는 그 안내데스크와 손님 사이에 앉아, 안내데스크가 건넨 진짜 답변지를 가로채 좌표만 바꿔 다시 손님에게 넘기는 중간 직원이다.
손님(아이폰)은 답변이 조작된 줄 모르고 그대로 믿는다. 이 "중간에 앉는 것"을 기술 용어로 MITM(중간자)이라 하고, wloc는 그걸 탈옥 없이 유료 프록시 앱의 정식 기능으로 해낸다.
gs-loc.apple.com/clls/wloc에 보내면, Apple이 "그 신호들이 있는 곳의 좌표"를 protobuf로 돌려주는 측위 방식. GPS가 안 잡히는 실내·지하에서 특히 중요하다. wloc라는 이름도 여기서 왔다(WiFi LOCation). — 이 응답을 조작하는 게 이 프로젝트의 핵심.위치를 바꾸는 기술은 프라이버시 보호(앱에 진짜 동선을 안 알려주기)처럼 정당한 쓰임도 있지만, 게임·서비스의 이용약관 위반이나 위치 기반 인증 회피에 악용될 수도 있다. 이 문서는 "Apple 네트워크 측위와 프록시 MITM·protobuf가 어떻게 작동하는가"라는 네트워크·리버싱 학습을 목적으로 한다. 실제 사용 시 각 서비스의 약관·현지 법규를 반드시 확인하고 책임은 사용자에게 있음을 전제로 읽자. (참고로 이 저장소에는 라이선스 파일이 없어 기본값은 "all rights reserved"다.)
iOS 위치 스푸핑은 새로운 주제가 아니다. 그런데 wloc가 이번 주 Weekly 18위(★ 4.3k, fork 807)까지 치고 올라온 건 기존 방법들의 고질적 불편을 정면으로 없앴기 때문이다. (TrendShift의 굵은 숫자는 별 수가 아니라 사회적 언급 모멘텀이다.) 먼저 기존 방법들이 왜 아팠는지부터 보자.
| 기존 방법 | 고통 포인트 |
|---|---|
| PC + 개발자 도구 | Xcode의 Simulate Location, 3uTools·AnyGo 같은 데스크톱 앱은 컴퓨터에 케이블로 연결해야 하고, 세션이 끊기면 원위치로 돌아간다. GPS 좌표만 다룬다. |
| 탈옥(Jailbreak) + 트윅 | Location Faker 류 트윅은 강력하지만, 최신 iOS는 탈옥 자체가 거의 불가능하고 뱅킹·보안 앱이 탈옥을 감지해 죽는다. |
| 개발자 모드 GPX | GPX 경로 파일을 물려 흉내내지만 설정이 번거롭고, 재부팅·재연결마다 반복해야 한다. |
중화권 iOS 사용자 다수는 이미 Surge·Quantumult X·Loon·Shadowrocket 같은 유료 프록시 앱을 쓴다. wloc는 별도 앱을 설치하라고 하지 않는다. 대신 그 프록시 앱들이 원래 제공하는 MITM + JavaScript 스크립트 기능에 설정 파일 하나만 얹으면 된다. "새 도구를 배우는 비용"이 거의 0에 가깝다.
대부분의 스푸핑 도구가 GPS 하드웨어를 흉내내려 애쓸 때, wloc는 관점을 뒤집었다. "아이폰이 Apple에 위치를 물어보는 그 요청/응답을 가로채면, 하드웨어를 건드리지 않고도 답을 바꿀 수 있다"는 것. 프록시는 어차피 모든 트래픽을 거치므로, 위치 조회 트래픽(/clls/wloc) 한 줄만 골라내 응답을 리라이트하면 그만이다. 발상 자체가 학습 가치가 높다.
위경도를 손으로 입력할 필요가 없다. Leaflet 지도 웹페이지에서 클릭·지명 검색·지도 링크 붙여넣기로 지점을 고르고 버튼 한 번이면 저장된다. 더 나아가 iOS 단축어(Shortcuts)를 등록하면 Apple 지도·高德(가오더) 지도의 공유 시트에서 바로 "이 위치로 전환"이 가능하다. "기술"이 아니라 "제품"처럼 다듬은 UX가 일반 사용자를 끌어들였다.
좌표 선택 페이지와 지도 링크 파싱을 담당하는 서버는 Cloudflare Worker/Pages 위에서 돌고, 사용자 좌표를 저장·로깅하지 않는다. 공용 인스턴스(wloc-pages.pages.dev)를 그냥 써도 되고, 프라이버시가 걱정되면 Cloudflare 무료 티어로 직접 배포하면 된다. "믿고 쓸 수 있는 구조"를 코드로 증명한 셈이다.
기존 GPS 스푸핑이 "내비게이션 위성 신호를 가짜로 쏘는 장비"를 만드는 일이었다면, wloc는 "목적지를 물어보는 전화선에 몰래 끼어들어 다른 주소를 불러주는 것"이다. 장비도, 케이블도 없이 이미 깔린 전화선(프록시)만 있으면 된다 — 이 접근의 가벼움이 트렌딩의 본질이다.
wloc의 스택은 세 개의 세계가 맞물려 있다. ① 프록시 앱 안에서 도는 클라이언트 스크립트(실제 리라이트를 하는 본체), ② 좌표를 고르고 지도 링크를 해석해주는 서버(선택적), ③ 이 둘이 상대하는 Apple의 위치 프로토콜. 하나씩 지도를 그려보자.
같은 번들 스크립트 하나가 런타임을 자동 감지해 6개 프록시 엔진에서 동작한다. dist/wloc.js 최상단이 어떤 앱 안에서 실행 중인지 전역 객체로 판별한다.
// dist/wloc.js — 어떤 프록시 앱 안에서 도는지 런타임 감지
const env = (() => {
const has = k => k in globalThis;
switch (true) {
case has("$task"): return "Quantumult X"; // QX 전용 전역
case has("$loon"): return "Loon";
case has("$rocket"): return "Shadowrocket";
case has("Egern"): return "Egern";
case Boolean(globalThis.$environment?.["surge-version"]): return "Surge";
case Boolean(globalThis.$environment?.["stash-version"]): return "Stash";
case has("Cloudflare"): return "Worker";
case Boolean(globalThis.process?.versions?.node): return "Node.js";
}
})();
| 엔진 | 설정 파일 | rewrite/script 방식 |
|---|---|---|
| Surge / Egern | modules/wloc.sgmodule | [Script] type=http-response(응답 패치) + type=http-request(설정 저장), [MITM] hostname |
| Quantumult X | modules/wloc.conf | [rewrite_local]의 script-response-body + script-echo-response, [mitm] |
| Loon | modules/wloc.lpx | [Script] http-response/http-request, [Argument] 입력폼, [MITM] |
| Stash | modules/wloc.stoverride | YAML http.script + script-providers(원격 스크립트 24h 자동 갱신) |
| Shadowrocket | modules/wloc.module | Surge 유사 [Script]/[MITM] |
엔진별로 API가 제각각이라, wloc는 NSNanoCat/Util이라는 크로스플랫폼 추상화 프레임워크를 번들해 Console(로그)·Storage(영구 저장소 래핑)·$done(응답 반환)을 한 겹으로 통일한다. "여러 플랫폼을 하나의 코드로"의 어댑터 패턴이다.
| 도구 | 역할 |
|---|---|
Hono ^4.10.6 | 초경량 웹 프레임워크. GET "/"(선택 페이지)와 GET "/api/parse"(지도 링크 → 좌표) 라우팅. hono/cloudflare-pages 어댑터로 Pages에도 배포. |
| Cloudflare Workers/Pages | 서버리스 실행 환경. wrangler로 배포, nodejs_compat 플래그. 무료 티어(하루 10만 요청)로 셀프호스팅 가능. 무상태·무로그. |
| Leaflet 1.9.4 | 선택 페이지의 인터랙티브 지도(unpkg CDN). 클릭으로 좌표 선택. |
| Nominatim (OSM) | OpenStreetMap의 지명 검색 API. "강남역" 같은 이름 → 좌표. |
| 브라우저 localStorage | 즐겨찾기 좌표를 브라우저에 저장(서버가 아님). 최대 30자 메모. |
navigator.geolocation | "현재 위치" 버튼 — 브라우저 표준 위치 API. |
| 요소 | 내용 |
|---|---|
| 엔드포인트 | gs-loc.apple.com · gs-loc-cn.apple.com(중국 본토용), 경로 /clls/wloc |
| 본문 형식 | Protocol Buffers(protobuf)를 Apple 고유 프레이밍(헤더 + 2바이트 길이 + payload)으로 감싼 바이너리 |
| 좌표 표현 | 위경도(도) × 1e8 정수를 protobuf varint로. 예: 위도 37.5도 → 3750000000 |
| 좌표계(datum) | 프로토콜 내부는 WGS-84. 중국 지도는 GCJ-02라 서버가 변환(§4-E) |
| MITM 대상 호스트 | 위 두 도메인만 복호화. 나머지 트래픽은 건드리지 않음 |
Apple은 WLOC의 .proto 스키마를 공개하지 않는다. §4에 나오는 필드 번호(위도=field 1, 경도=field 2 등)는 wloc의 파서 코드가 실제로 어떤 필드를 읽고 쓰는지를 관찰해 재구성한 것이다. Apple이 프로토콜을 바꾸면 도구가 깨질 수 있고, 그래서 wloc의 파서는 뒤에서 보듯 방어적으로(브루트포스 + 폴백) 짜여 있다.
모듈은 프록시에 규칙을 두 개 건다. 하나는 응답을 가로채는 것, 다른 하나는 요청을 가로채는 것이다.
| 규칙 | 대상 | 하는 일 |
|---|---|---|
wloc.js | /clls/wloc 응답(http-response) | Apple이 돌려준 protobuf를 파싱 → 좌표 필드를 목표값으로 치환 → 재직렬화 |
wloc-settings.js | /wloc-settings/save 요청(http-request) | 이 경로는 Apple에 실재하지 않는 "가짜 경로". 선택 페이지가 일부러 이 URL을 때리면 프록시가 낚아채 좌표를 저장하고 합성 JSON을 즉시 반환 |
좌표를 저장하려면 어딘가에 값을 넘겨야 하는데, 웹페이지(브라우저)는 프록시의 저장소에 직접 못 쓴다. 그래서 wloc는 이미 MITM 대상으로 잡아둔 호스트(gs-loc.apple.com)를 재활용한다. 페이지가 gs-loc.apple.com/wloc-settings/save?lon=..&lat=..로 요청을 쏘면, 실제 Apple로 가기 전에 프록시가 가로채(어차피 그 호스트를 감시 중이니) 좌표를 저장한 뒤 가짜 성공 응답을 돌려준다. 진짜 Apple 서버엔 이 요청이 도달하지 않는다. 같은 MITM 규칙 하나로 "저장 채널"까지 만든 영리한 재활용.
wloc의 파서가 드러내는 실제 스키마는 다음과 같다. protobuf는 각 필드를 tag = (field번호 << 3) | wire타입으로 표시하는데, wloc는 이 규칙으로 바이트를 훑어 "좌표처럼 생긴 레코드"만 골라 값을 갈아끼운다.
핵심 패치 함수(가독화). "좌표 레코드가 맞는지" 먼저 확인하고, 맞을 때만 값을 바꾼 뒤 나머지 필드는 원본 그대로 재직렬화한다 — 모르는 필드를 함부로 버리지 않는 게 포인트다.
// 위치 레코드 리라이트 — lat/lon 둘 다 있을 때만 패치
function patchLocation(buf, target, stat) {
const fields = readFields(buf); // protobuf → 필드 배열
let hasLat = false, hasLon = false;
for (const f of fields) {
if (f.fieldNo === 1 && f.wireType === 0) hasLat = true; // latitude 존재?
if (f.fieldNo === 2 && f.wireType === 0) hasLon = true; // longitude 존재?
}
if (!hasLat || !hasLon) return buf; // 좌표 레코드 아니면 그대로 통과
const out = [];
for (const f of fields) {
if (f.fieldNo === 1 && f.wireType === 0) out.push(enc(1, 0, Math.round(1e8 * target.latitude)));
else if (f.fieldNo === 2 && f.wireType === 0) out.push(enc(2, 0, Math.round(1e8 * target.longitude)));
else if (f.fieldNo === 3 && f.wireType === 0) out.push(enc(3, 0, target.accuracy));
else out.push(f.raw); // 모르는 필드는 원본 유지
}
stat.locations++;
return serialize(out);
}
Apple 프레임을 벗겨내고 다시 싸는 부분. 2바이트 big-endian 길이 프리픽스가 이 프레이밍의 특징이다.
// [헤더 8B][길이 2B BE][protobuf payload][꼬리] 를 벗겨 패치 후 재조립
function unwrapFrame(buf, base, target, stat) {
if (buf.length < base + 10) throw new Error("body too short");
const len = (255 & buf[base+8]) << 8 | (255 & buf[base+9]); // 2바이트 BE 길이
if (len <= 0 || len + base + 10 > buf.length) throw new Error("invalid frame length");
const head = buf.slice(0, base + 8); // 헤더
const payload = buf.slice(base+10, base+10+len); // protobuf 본문
const tail = buf.slice(base+10+len); // 꼬리
const patched = patchTopLevel(payload, target, stat);
if (patched.length > 65535) throw new Error("payload too large");
return serialize([head, [patched.length >> 8 & 255, 255 & patched.length], patched, tail]);
}
protobuf varint 리더. 7비트씩 쌓고 최상위 비트가 "다음 바이트가 이어짐"을 뜻하는 표준 인코딩이다.
// readVarint → [값, 다음 오프셋]
function readVarint(buf, i) {
let val = 0, mul = 1, bits = 0;
while (i < buf.length) {
const b = 255 & buf[i++];
if (bits < 56) val += (127 & b) * mul; // 하위 7비트 누적
if (!(128 & b)) return [val, i]; // 최상위 비트 0 = 끝
mul *= 128;
if ((bits += 7) >= 70) throw new Error("varint too long");
}
throw new Error("truncated varint");
}
Apple 응답의 헤더 길이는 고정이 아니다. 그래서 wloc는 base offset 후보를 0, 2, 4 … 16까지(경우에 따라 0~96) 차례로 시도하며 유효한 프레임이 나올 때까지 브루트포스하고, 그래도 실패하면 raw protobuf 스캔(offset 0~256)으로 폴백한다. 스키마가 공개되지 않은 프로토콜을 상대로 "깨지지 않게" 짜는 실전 리버싱 기법의 표본이다.
설정이 비었거나 좌표가 기본값(sentinel)이면, 응답을 건드리지 않고 그대로 통과시켜 진짜 위치를 복원한다. 기본값은 경도 113.94114, 위도 22.544577(중국 선전 바오안) — 이 좌표가 곧 "미설정" 신호다.
// 저장값이 비었고 모듈 인자가 기본값이면 → 좌표를 null로 만들어 passthrough
if (cfg.longitude === 113.94114 && cfg.latitude === 22.544577) {
cfg.longitude = null; cfg.latitude = null;
log.info("[settings] 透传 모드: 저장값 없음 + 기본 인자 → 위치 수정 안 함");
}
// 응답 패치 직전: 좌표가 null이면 원본 응답 그대로 반환
if (cfg.longitude == null || cfg.latitude == null) {
log.info("[wloc] 透传 모드: 좌표 미설정 → 실제 위치 복원");
return originalResponse;
}
중국은 규제상 지도 좌표에 의도적 오차를 넣은 GCJ-02(속칭 火星坐标, "화성 좌표")를 쓴다. 高德·중국 Apple 지도에서 뽑은 좌표를 그대로 주입하면 실제 위치가 수백 미터 어긋난다. 그래서 서버가 GCJ-02 → WGS-84 역변환을 한다. 변환식에 닫힌 역함수가 없어, 불동점 반복(fixed-point iteration)으로 근사한다.
// worker/src/parse.js — GCJ-02 → WGS-84 (반복 역산으로 수렴)
function gcj02ToWgs84(lat, lon) {
if (outOfChina(lon, lat)) return { lat, lon }; // 중국 밖이면 변환 불필요
let wLat = lat, wLon = lon;
for (let i = 0; i < 6; i++) { // 6회면 오차 < 0.1m로 수렴
const g = wgs84ToGcj02(wLat, wLon); // 정방향 변환은 공식이 있음
const errLat = g.lat - lat, errLon = g.lon - lon;
if (Math.abs(errLat) < 1e-9 && Math.abs(errLon) < 1e-9) break;
wLat -= errLat; wLon -= errLon; // 오차만큼 되밀기
}
return { lat: wLat, lon: wLon };
}
지도 링크에서 좌표를 뽑는 부분도 흥미롭다. Apple 링크는 URL 쿼리(coordinate=/ll=/sll=)에 좌표가 있지만, 高德 단축링크는 302 리다이렉트의 Location 헤더에만 좌표가 담겨 있어 redirect:"manual"로 최대 5회까지 따라가며 추출한다.
같은 "위도 39.9, 경도 116.4"라도 어느 좌표계로 읽느냐에 따라 실제 지점이 수백 미터 다르다. 세계 표준(GPS·WGS-84)과 중국 규정(GCJ-02)이 다르기 때문. 이걸 무시하고 중국 지도 좌표를 그대로 넣으면 "천안문 앞"이 "천안문에서 300m 떨어진 골목"이 된다. wloc가 굳이 반복 역산까지 넣은 건, 사용자가 익숙한 중국 지도에서 점을 찍어도 정확히 그 자리로 가게 하려는 배려다.
modules/) · 실제 리라이트 스크립트(dist/) · 선택 페이지 서버(worker/)구조가 프로젝트의 정체성을 요약한다. 실제로 "위치를 바꾸는" 두뇌는 dist/의 스크립트 2개이고, modules/는 그 두뇌를 각 프록시 앱에 꽂는 어댑터, worker/는 "어디로 옮길지 고르는" 편의 UI다. 두뇌만 있으면 서버 없이도 동작하지만(모듈 인자로 좌표 직접 입력), 서버가 있으면 "지도에서 점 찍기"라는 사용성을 얻는다 — 기능과 사용성이 깔끔하게 분리돼 있다.
dist/의 .js는 사람이 직접 쓴 원본이 아니라, 여러 소스와 NSNanoCat/Util 프레임워크를 합쳐(bundle) 공백·주석을 제거하고 변수명을 줄인(minify) 배포용 산출물이다. 그래서 코드가 e, Oe, Ue처럼 짧은 이름으로 보인다. §4의 스니펫은 이를 사람이 읽기 좋게 되돌려 놓은 것.wloc는 코드량이 크지 않지만(핵심 소스 약 1,500줄), 다루는 개념의 밀도가 높다. 아래는 제품을 쓰지 않아도 개발자에게 남는 지식이다.
왜 "인증서를 신뢰"시키면 암호화된 HTTPS를 들여다볼 수 있는지, 왜 특정 호스트만 골라(gs-loc.apple.com) 복호화하는지. TLS 신뢰 사슬과 중간자의 원리를 손으로 만지며 이해한다. 보안 학습의 정석 주제.
Surge·QX·Loon·Stash가 같은 일을 얼마나 다른 문법으로 하는지(http-response vs script-response-body vs YAML script), binary-body-mode·$persistentStore·$done 같은 공통 개념, 그리고 이걸 하나의 코드로 추상화하는 어댑터 패턴(NSNanoCat/Util).
.proto 파일 없이 바이트만 보고 wire type(0=varint, 2=length-delimited …)과 필드 번호를 파싱하는 법, tag = (field << 3) | wireType 규칙, varint 인코딩, 그리고 아는 필드만 골라 바꾸고 모르는 필드는 보존하는 부분 패치 기법. 바이너리 프로토콜 리버싱의 축소판이다.
zlib 없이 순수 JavaScript gzip inflate, Buffer 없이 바이트 배열 조작, 스키마가 흔들려도 안 깨지게 하는 브루트포스 + 폴백 방어 파싱. "라이브러리가 없을 때 바닥부터 짜는" 근육을 기른다.
GPS ≠ 네트워크 위치라는 사실, WLOC이 WiFi MAC + 셀 타워를 Apple에 보내 좌표를 받는 구조, locationd 캐시(§7). "아이폰은 어떻게 실내에서도 위치를 아는가?"의 답을 배운다.
WGS-84 vs 중국 GCJ-02 오프셋, outOfChina 판정, 그리고 닫힌 역함수가 없을 때 불동점 반복으로 역변환을 근사하는 수치기법. 지리 데이터를 다루는 누구나 한 번은 부딪히는 함정이다.
Hono + Cloudflare Workers/Pages로 초경량 API를 짜고, redirect:"manual"로 단축링크 302를 추적하며, 사용자 데이터를 저장하지 않는 프라이버시 우선 서버를 만드는 법.
wloc의 진짜 알맹이는 §4-C의 dist/wloc.js 파서다. 스키마 없는 바이너리를 어떻게 안전하게 파싱·수정·재직렬화하는가는 위치와 무관하게 통용되는 기술이다. 이 패턴은 게임 패킷 분석, IoT 프로토콜 리버싱, 레거시 포맷 파싱 등 어디에나 옮겨진다. "무엇을 바꾸는가"보다 "어떻게 바꾸는가"에 집중하면 가장 많이 남는다.
| 항목 | 필요한 것 / 유의점 |
|---|---|
| 기기 | iPhone / iPad (iOS). 탈옥 불필요, 컴퓨터 불필요. |
| 프록시 앱(택1) | Surge / Quantumult X / Loon / Stash / Shadowrocket / Egern — 전부 유료 App Store 앱. MITM + JS 스크립트 지원이 필수 조건. |
| MITM CA 인증서 | 프록시 앱의 루트 인증서를 설치하고 신뢰 설정(gs-loc.apple.com, gs-loc-cn.apple.com 대상). 프록시(VPN 프로파일)가 실제로 구동 중이어야 함. |
| iOS 버전 주의 | iOS 15~18은 대체로 즉시 반영. iOS 26/27+는 locationd가 위치를 메모리에 오래 캐시해, 비행기모드·위치 토글로는 안 지워지고 기기 재부팅이 필요할 수 있음. |
| 선택 페이지/파싱 서버 | 공용 인스턴스(wloc-pages.pages.dev)를 그대로 쓰면 컴퓨터 없이 완결. 직접 배포할 때만 npm/wrangler와 Cloudflare 계정 필요(무료 티어 가능). |
| 작동 범위 한계 | WiFi/기지국 네트워크 위치만 수정. GPS 하드웨어는 안 건드림 → GPS 신호가 강하면 조작이 무시될 수 있음. 실내·WiFi 측위 환경에서 가장 잘 통한다. |
아이폰의 최종 위치는 GPS·WiFi·셀·블루투스를 종합해 결정된다. wloc는 그중 네트워크(WLOC) 응답만 바꾸므로, 야외에서 GPS가 쨍하게 잡히면 조작 좌표가 밀려날 수 있다. 또한 앱에 따라 위치 신뢰도를 교차검증하기도 한다. "어디서나 100% 통하는 도구"로 오해하면 실망하기 쉽다 — 원리(§4)를 알면 왜 그런지 자연히 이해된다.
§4-C의 readVarint를 참고해, 임의의 정수(예: 3750000000 = 위도 37.5×1e8)를 varint 바이트열로 인코딩했다가 다시 디코딩하는 함수를 Node.js로 짜보자. "7비트씩 쌓고 최상위 비트로 이어짐을 표시"하는 규칙을 손으로 확인하면 protobuf가 더 이상 마법처럼 보이지 않는다.
worker/src/parse.js의 정방향 변환식(공개 알고리즘)을 옮기고, §4-E의 불동점 반복으로 역변환을 구현하자. 서울·베이징 좌표를 넣어 "정방향 → 역방향" 왕복 오차가 얼마나 작은지 측정해보면, 닫힌 역함수가 없어도 반복으로 충분히 정밀해지는 걸 체감한다.
브라우저·Node의 zlib이 없다고 가정하고, gzip 매직 넘버(0x1f 0x8b)를 감지하는 코드부터 시작해보자. 전체 inflate 구현은 무겁지만, "왜 프록시 스크립트 런타임에선 표준 라이브러리를 못 쓰는가 → 그래서 무엇을 자체 구현해야 하는가"를 조사·정리하는 것만으로도 제약 런타임의 현실을 배운다.
Apple 위치 대신 내가 소유한 테스트 서버나 공개 JSON API를 대상으로, 프록시 앱(또는 mitmproxy)의 http-response 스크립트를 걸어 응답 본문의 한 필드를 바꿔보자. MITM 인증서 신뢰 → 특정 호스트 복호화 → 스크립트로 바디 수정의 전 과정을 자기 데이터로 안전하게 재현하는 게 핵심. (남의 서비스·타인의 트래픽은 대상 금지.)
worker/를 본떠, 지도 링크(또는 임의 URL)를 받아 좌표·메타데이터를 뽑아 JSON으로 돌려주는 저장·로그 없는 엣지 API를 직접 배포하자. redirect:"manual"로 단축링크 302를 따라가는 부분과 CORS 헤더 처리까지 넣으면, 서버리스 파싱 서비스의 골격을 완성한다.
| 주차 | 주제 | 핵심 질문 · 자료 |
|---|---|---|
| 1주차 | HTTPS·TLS와 MITM | 인증서 신뢰 사슬은 왜 "중간자"를 가능케 하나? → mitmproxy 공식 문서 + wloc의 [MITM] hostname 설정. |
| 2주차 | Protocol Buffers 내부 | wire type, tag = (field<<3)|type, varint, length-delimited. → protobuf encoding 공식 문서 + dist/wloc.js 파서. |
| 3주차 | iOS 위치 측위 스택 | GPS vs WLOC vs 블루투스 비콘, locationd·CoreLocation. → Apple 위치 서비스 문서 + docs/shortcut-guide.md. |
| 4주차 | 서버리스 엣지 + 지리 좌표계 | Hono·Cloudflare Workers 배포, WGS-84/GCJ-02 변환. → Hono·Wrangler 문서 + worker/src/parse.js. |
먼저 MITM으로 "암호화된 트래픽을 어떻게 들여다보나"를 잡아야 나머지가 의미를 갖는다(1주차). 그다음 protobuf로 "가로챈 바이너리를 어떻게 해석·수정하나"(2주차). 마지막으로 측위·좌표계로 "왜 이 필드가 위치이고, 왜 좌표계 변환이 필요한가"(3~4주차)를 채우면 전체 그림이 닫힌다. 순서를 지키면 각 주차가 다음 주차의 벽돌이 된다.
같은 "네트워크·프록시" 결에서 위치를 잡아보자. router-for-me/CLIProxyAPI·diegosouzapw/OmniRoute가 "요청을 다른 API로 중계·변환하는" 프록시라면, wloc는 "응답을 가로채 수정하는" 프록시 스크립트다 — 둘 다 프록시가 트래픽의 길목이라는 성질을 활용한다. 리버싱·바이너리 파싱 관점에선 hmjz100/LinkSwift(웹 API를 뜯어 다운로드 링크를 뽑는 스크립트)와, 서버리스·엣지 관점에선 Cloudflare Workers 기반 도구들과 나란히 놓고 보면 "프록시라는 자리를 어떻게 쓰는가"의 스펙트럼이 보인다.
gs-loc.apple.com/clls/wloc에 보내 좌표를 받는 측위 방식. GPS가 약한 실내에서 중요. wloc가 조작하는 대상.tag=(field<<3)|wireType + 값으로 표현. Apple WLOC 응답이 이 형식이라, wloc가 스키마 없이 역공학해 파싱한다.http-response, QX script-response-body 등 엔진별 문법이 다르다.binary-body-mode=1로 켠다.^4.10.6).Console·Storage·$done으로 통일해주는 크로스플랫폼 프레임워크. wloc가 번들해 "한 코드, 여러 엔진"을 실현.| 구분 | 링크 / 위치 |
|---|---|
| GitHub 저장소 | github.com/Yu9191/wloc |
| TrendShift | trendshift.io/repositories/66089 |
| 선택 페이지(공용) | wloc-pages.pages.dev · wloc-spoofer.wloc.workers.dev |
| 핵심 파일(두뇌) | dist/wloc.js (응답 패처) · dist/wloc-settings.js (설정 저장) |
| 핵심 파일(서버) | worker/src/parse.js (좌표추출·좌표계 변환) · page.js (Leaflet UI) |
| 엔진별 설정 | modules/wloc.sgmodule(Surge) · wloc.conf(QX) · wloc.lpx(Loon) · wloc.stoverride(Stash) · wloc.module(Shadowrocket) |
| 단축어 가이드 | docs/shortcut-guide.md |
| 원조 아이디어 | github.com/FFF686868/proxypin-wloc-spoofer (ProxyPin 기반 원조 WLOC 스푸핑) |
| 스크립트 프레임워크 | github.com/NSNanoCat/util |
| 연관 개념 | Protocol Buffers · MITM(mitmproxy) · WGS-84/GCJ-02 · Hono · Cloudflare Workers · Leaflet · OSM Nominatim |