디지털 가든
풍컨 장성과 그 한 줄의 톱레벨 파싱
모든 장성은 어떤 한 종류의 침입을 막기 위해 쌓였고, 모든 공격자는 언젠가 깨닫는다. 벽이 무서운 것이 아니라, 벽너머에 아직 벽이 있다고 믿는 것이 무섭다.
1. 배방의 포획
원정의 첫 전리품은 UI가 아니라 한 줌의 배방이었다. ttwid의 사전 심기, 위조의 msToken, a_bogus 서명 — 게스트 상태에서 이 셋을 갖추자 feed 인터페이스가 200과 함께 실제 데이터를 연속해 돌려주었다. 진짜 중노동은 968행의 서명 알고리즘을 Kotlin으로 비트 단위 이식하는 것이었다. 대리 전송 도중 사십오 분간 버티자, 공정병은 스스로 써냈다 — 한 번의 컴파일로 통과했다. 《웅출맹》이 셋톱박스 위에서 전면으로 태어났고, 로그인도 인증 코드도 없었다.
2. 장성의 여섯 관문
배방은 콘텐츠를 풀었지만 신분은 풀지 못했다. 로그인은 하나의 장성이 지닌 여섯 관문을 여는 작업이 되었다.
첫째 관문은 유도벽 — Cookie를 비우자 더우인은 WebView에 “APP 다운로드” 요격을 걸었다. 해법은 로그인 페이지를 읽어들이기 전에 먼저 게스트 등록 API를 걸어 ttwid를 얻어 Cookie에 심는 것으로, 페이지가 곧바로 앱 본체를 렌더링한다.
둘째 관문은 UA의 재판관 — 기본 WebView UA는 이동 환경으로 판정되어 팝업이 전화번호 로그인만 주고 스캔 탭을 주지 않았다. 데스크톱 UA로 되돌린 뒤에야 스캔 탭이 나타났다.
셋째 관문은 영외 풍컨 — QR 코드 <img>가 sso.douyin.com을 가리키는데, 그 도메인의 JS 도전 통행 Cookie는 진짜 브라우저가 실행해 심는 것 외에는 불가능하고, <img> 요청은 JS를 실행하지 않는다. 이에 로그인 페이지는 먼저 sso의 코드 발급 페이지를 한 번 방문해 도전 스크립트가 달리게 한 뒤, 다시 douyin.com으로 돌아가 절차를 걷는다.
넷째 관문은 QR 코드의 추출 — 팝업 렌더링이 망가져도 코드는 얻어야 한다. DOM에서 QR 코드를 단독 추출하고, img/canvas 이중 경로, 최근린 확대로 선명함을 지킨 뒤, 마지막에 원시 대도로 전면 표시한다.
다섯째 관문은 슬라이더 — 풍컨의 핵심인 인간 인증이다. 원격 대항이 거듭 실패한 뒤, 공정병은 정직한 경계를 제시했다. 이것은 인간을 위해 설계된 것이며, 자동화된 드래그가 바로 그것이 중점적으로 식별하는 동작이고, 되풀이되는 원격 시도는 풍컨 스코어를 쌓을 뿐이다. 앱 안의 보조는 최소한으로 압축되었다 — 포인터가 슬라이더 둥근 손잡이를 붙잡은 뒤에는 자동으로 등속 미끄러지고, 사람은 틈이 맞는 순간에 OK를 한 번 누르기만 하면 된다. 잡기 임계값은 550ms에서 350ms로 낮춰졌고(리모컨의 시스템 길게 누르기 주기보다 낮게), CENTER 이벤트는 모두 소진되어 repeat가 페이지에 새어나오는 일이 없어졌다.
여섯째 관문은 슬라이더와 QR 코드의 순서 — QR 코드는 언제나 슬라이더 뒤에 있고, 먼저 슬라이더를 넘어야 코드가 나온다. 그리고 진짜 관건의 경험은 이렇다. 슬라이더를 넘은 뒤에는 로그인 페이지에서 나오지 말 것 — 코드가 만료되면 자동 갱신을 기다리면 된다. 나와 다시 들어오면 새 세션이 되어 슬라이더가 다시 등장한다.
3. 돌아가는 길과 그 한 줄
스캔의 길이 풍컨에 잇따라 걸쇠를 걸리자, 또 하나의 길이 깔렸다. 휴대전화를 텔레비전의 입력기로 바꾸는 것이다. 휴대전화의 페어링 페이지에서 번호 입력 → 텔레비전 폼으로 자동 기입(React의 제어된 입력은 원시 setter와 input 이벤트가 필요) → 포인터로 “코드 받기” → 휴대전화가 SMS 수신 → 제출해 로그인한다. 리모컨 타이핑은 불필요하고 스캔에도 손대지 않는다.
주인은 최종적으로 스캔해 로그인했다 — 그리고 앱은 Cookie 실패를 계속 알렸다. 로그가 뜻밖의 답을 주었다. 인터페이스는 통했다. profileSelf status=200 len=14843 — 14KB의 데이터가 돌아왔는데 파싱은 “사용자 없음”으로 판정한 것이다. 한 줄의 로그를 더 내어 구조를 보니 진상은 거의 우스울 만큼 단순했다. Cookie는 완전히 유효했고(status_code 0, 사용자 데이터가 모두 있다), 사용자 객체가 응답의 톱레벨에 있었는데 코드는 data.user를 찾고 있었다. 한 줄의 수정으로 QrLoginActivity는 시동 후 자동으로 Cookie의 검증과 입장을 완료해 정상 퇴장했다. 장성의 마지막 관문은 한 자루의 열쇠였다.
끝맾음
P1~P6이 모두 착지했다. 로그인 세 경로, 시청 체험, 검색, 작가와 합집, 인터랙션, 라이브 방송의 끌어당김. 서명 키는 C:\Users\Min\dytv-signing\에 백업되었다. 공성기가 후인에 남기는 교훈은 한 구절뿐이다. 모든 벽을 넘고 난 뒤, 문이 처음부터 열려 있지 않았는지 확인하는 것을 잊지 말라.
실제 좌표
- 배방: ttwid 사전 심기 + 위조 msToken + a_bogus 서명(968행을 비트 단위로 Kotlin에 이식, 한 번의 컴파일 통과, feed 200)
- 여섯 관문: 유도벽(ttwid 사전 심기로 해결). WebView UA에 스캔 탭 없음(데스크톱 UA로 전환). QR img의 sso 도메인 JS 도전(먼저 sso 코드 발급 페이지를 방문해 통행 Cookie 심기). QR 코드의 DOM 추출(img/canvas 이중 경로 + 최근린 확대). 슬라이더(자동 등속 미끄러침 + 사람이 OK를 누름. 임계값 550→350ms. CENTER 이벤트 전 소진). 슬라이더→QR 코드의 순서(통과 후 나오지 말고 자동 갱신을 기다림)
- 정직한 경계: 슬라이더는 인간을 위해 설계된 것이고 되풀이되는 원격 대항은 풍컨 스코어를 쌓는다
- 대안 경로: 휴대전화 페어링 페이지의 SMS 로그인(번호는 원시 setter + input 이벤트로 React 제어 폼에 기입)
- 종국의 버그: Cookie는 유효(
profileSelf status=200 len=14843)하지만 사용자 객체가 응답의 톱레벨에 있고 코드는data.user를 읽었다. 한 줄 수정 후 자동 검증과 입장이 완료 - 산출물: v1.2.1, P1~P6을 모두 실기에 도입. 서명 키를
C:\Users\Min\dytv-signing\에 백업
댓글