Shape에서 우리는 JavaScript의 많은 모호한 부분을 접하게 됩니다. 우리는 페이지에 악의적으로 삽입된 스크립트를 찾을 수도 있고, 고객이 조언을 구하기 위해 보낸 스크립트일 수도 있고, 보안팀이 웹에서 우리 서비스의 특정 측면을 구체적으로 참조하는 것으로 보이는 리소스를 찾을 수도 있습니다. 일상 생활의 일부로, 우리는 스크립트를 맨 처음부터 읽어서 스크립트가 무엇을 하는지, 어떻게 작동하는지 이해합니다. 이러한 데이터는 일반적으로 최소화되고, 종종 난독화되며, 심층 분석에 적합할 때까지 항상 여러 수준의 수정이 필요합니다.
얼마 전까지만 해도 이러한 분석을 수행하는 가장 쉬운 방법은 수동 편집이 가능한 로컬 캐시 설정을 사용하거나 프록시를 사용하여 즉석에서 콘텐츠를 다시 작성하는 것이었습니다. 로컬 솔루션이 가장 편리하지만, 웹사이트가 항상 다른 환경에 완벽하게 변환되는 것은 아니며, 생산성을 높이기 위해 문제 해결에 몰두하는 경우가 많습니다. 프록시는 매우 유연하지만 일반적으로 다루기 어렵고 휴대성이 떨어집니다. 모든 사람이 자신의 환경에 맞게 사용자 정의 설정을 갖고 있으며, 일부 사람은 특정 프록시에 더 익숙합니다. 저는 요청과 응답이 발생하는 순간에 이를 파악하고 즉시 수정하기 위해 Chrome과 DevTools 프로토콜을 사용하기 시작했습니다. 이 기능은 Chrome이 있는 모든 플랫폼에 이식이 가능하며, 많은 문제를 해결하고 일반적인 JavaScript 도구와도 잘 통합됩니다. 이 게시물에서는 Chrome DevTools 프로토콜을 사용하여 JavaScript를 즉석에서 가로채고 수정하는 방법을 살펴보겠습니다.
우리는 노드를 사용하겠지만 DevTools Hooks에 쉽게 접근할 수 있다면 많은 콘텐츠를 선택한 언어로 이식할 수 있습니다.
우선, Chrome 스크립팅을 한 번도 시도해 본 적이 없다면 Eric Bidelman이 작성한 Headless Chrome 시작 가이드를 참조하세요. 여기의 팁은 Headless Chrome과 GUI Chrome에 모두 적용됩니다(다음 섹션에서 한 가지 특이한 점을 다루겠습니다).
npm의 chrome-launcher 라이브러리를 사용하면 이 작업을 쉽게 수행할 수 있습니다.
chrome-launcher는 여러분이 생각하는 대로 정확하게 작동하며, 터미널에서 익숙한 명령줄 스위치를 그대로 사용하면 됩니다( 여기에서 훌륭한 목록을 확인할 수 있습니다 ). 다음 옵션을 전달하겠습니다.
–창 크기=1200,800
–auto-open-devtools-for-tabs
–user-data-dir=/tmp/chrome-testing
스크립트를 실행하여 Chrome을 열 수 있는지 확인하세요. 다음과 같은 내용이 표시되어야 합니다.
이를 "크롬 디버거 프로토콜"이라고도 하며, 두 용어 모두 Google 문서에서 서로 바꿔 사용되는 것으로 보입니다. 먼저, npm을 통해 chrome-remote-interface 패키지를 설치합니다. 이를 통해 DevTools 프로토콜과 편리하게 상호작용할 수 있습니다. 더 자세히 알아보려면 프로토콜 문서를 준비해 두세요.
CDP를 사용하려면 디버거 포트에 연결해야 합니다. chrome-launcher 라이브러리를 사용하고 있으므로 chrome.port 를 통해 편리하게 접근할 수 있습니다.
프로토콜의 많은 도메인을 먼저 활성화해야 하며, 콘솔 API에 연결하고 브라우저에서 명령줄로 모든 콘솔 호출을 전달할 수 있도록 런타임 도메인부터 시작하겠습니다.
이제 스크립트를 실행하면 모든 콘솔 메시지를 터미널에 출력하는 완벽한 기능을 갖춘 Chrome 창이 나타납니다. 그 자체로 훌륭하죠, 특히 테스트 목적으로 사용하면요!
먼저, setRequestInterception 에 RequestPatterns 목록을 제출하여 가로채고 싶은 것을 등록해야 합니다. "요청" 단계나 "헤더 수신" 단계에서 가로챌 수 있으며, 실제로 응답을 수정하려면 "헤더 수신"을 기다려야 합니다. 리소스 유형은 DevTools의 네트워크 창에서 일반적으로 볼 수 있는 유형 에 매핑됩니다.
위의 Runtime 에서와 마찬가지로 Network.enable()을 같은 배열에 추가하여 Network 도메인을 활성화하는 것을 잊지 마세요.
이벤트 핸들러를 등록하는 것은 비교적 간단하며, 가로채는 각 요청에는 요청에 대한 정보를 쿼리하거나 결국 연속을 실행하는 데 사용할 수 있는 interceptionId가 제공됩니다. 여기서는 우리가 개입해서 터미널에 가로채는 모든 요청을 기록합니다.
요청을 수정하려면 base64 문자열을 인코딩하고 디코딩하는 도우미 라이브러리를 설치해야 합니다. 이용할 수 있는 라이브러리가 아주 많으니, 마음껏 선택하세요. 우리는 atob 과 btoa를 사용할 것입니다.
응답을 처리하는 API가 약간 어색합니다. 응답을 처리하려면 요청 가로채기에 모든 응답 로직을 포함해야 합니다(예를 들어, 단순히 응답을 가로채는 것과는 반대로).그런 다음 가로채기 ID로 본문을 쿼리해야 합니다.핸들러가 호출될 때 본문을 사용할 수 없을 수 있기 때문이며, 이렇게 하면 찾고 있는 내용이 나올 때까지 명시적으로 기다릴 수 있습니다. 본문은 base64로 인코딩되어 있을 수도 있으므로 무작정 전달하기 전에 확인하고 디코딩해야 합니다.
이 시점에서는 JavaScript를 마음껏 사용해도 됩니다. 귀하의 코드는 요청된 JavaScript 전체에 접근하고 수정된 응답을 다시 보낼 수 있도록 응답 중간에 위치합니다. 엄청난! 수정된 코드가 브라우저에서 실행될 때 터미널에서 메시지를 받을 수 있도록 console.log를 끝에 추가하여 JS를 조정하겠습니다.
원래 리소스와 함께 전송된 헤더와 내용이 충돌할 수 있으므로 수정된 본문만 전달할 수는 없습니다. 여러분이 적극적으로 테스트하고 수정하고 있기 때문에, 전달해야 할 다른 헤더 정보에 대해 너무 걱정하기 전에 기본 사항부터 시작하는 것이 좋습니다. 필요한 경우 이벤트 핸들러에 전달된 responseHeaders를 통해 응답 헤더에 액세스할 수 있지만, 지금은 나중에 쉽게 조작하고 편집할 수 있도록 배열로 최소한의 세트를 직접 만들 것입니다.
새로운 응답을 보내려면 전체 base64 인코딩된 HTTP 응답(HTTP 상태 줄 포함)을 작성하고 continueInterceptedRequest 에 전달된 객체의 rawResponse 속성을 통해 보내야 합니다.
이제 스크립트를 실행하고 인터넷을 탐색하면 스크립트가 JavaScript를 가로채고 수정된 JavaScript가 브라우저에서 실행되면서 튜토리얼 시작 부분에서 만든 후크를 통해 console.log() 가 올라오는 모습과 비슷한 내용이 터미널에 표시됩니다.
기본 예제에 대한 전체 작동 코드는 다음과 같습니다.
소스 코드를 예쁘게 인쇄하는 것부터 시작할 수 있습니다. 이는 역엔지니어링을 시작하는 데 항상 유용한 방법입니다. 물론입니다. 대부분의 최신 브라우저에서 이 작업을 수행할 수 있지만 브라우저와 브라우저 버전 간 일관성을 유지하고 소스를 분석하면서 요점을 파악할 수 있도록 각 수정 단계를 직접 제어하고 싶을 것입니다. 외국의 난독화된 코드를 파헤칠 때, 그 목적을 이해하기 시작하면서 변수와 함수의 이름을 바꾸는 것을 좋아합니다. JavaScript를 안전하게 수정하는 것은 간단한 작업이 아니며 이에 대해서는 블로그 게시물에서 별도로 설명할 필요가 있지만 지금은 unminify 와 같은 명령을 사용하여 일반적인 축소 및 난독화 기술을 실행 취소할 수 있습니다.
npm을 통해 unminify를 설치하고 새로운 JavaScript 본문을 unminify 를 호출하여 래핑하면 실제로 어떻게 동작하는지 볼 수 있습니다.
다음 게시물에서는 변형에 대해 더 자세히 알아보겠습니다. 질문이나 의견이 있으시거나 다른 좋은 아이디어가 있으시다면 Twitter를 통해 연락주세요!