네이버 사다리와 함께 했던 워크숍 (1)

2024. 3. 23. 17:55취미로 하는 개발

얼마 전 1박 2일로 회사 워크숍을 다녀왔다.

워크숍 운영진으로서 재미있는 워크숍을 위해서는 뭐가 필요할지 고민했는데, 네이버 사다리 타기 결과를 조작해서 친애하는 부대표님에게 설거지와 노래 부르기(ㅋㅋ)를 몰아주면 재미있을 것 같았다.

 

그래서 네이버 사다리 결과를 원하는 대로 만들기 위한 작업에 착수했다.

('네이버 사다리'는 네이버 PC에서 '사다리 타기 게임' 등의 키워드를 통해 검색해서 즐길 수 있는 사다리 게임을 말한다.)

네이버 사다리 게임. 모바일과 PC가 다르다.

구현 요구사항은 아주 심플하다.

타깃의 이름과 목표로 하는 키워드(당첨)가 일치하게 출력되는 것

 

기술적으로 가능한지를 먼저 검토하기 위해 네이버 사다리 구현을 살펴보자.

사다리 게임은 캔버스를 통해 렌더링이 되고 있다.

40

캔버스를 그리는 로직이 있길 바라면서 dom을 살펴보니 바로 아래에 매우 의심스러운 script가 하나 보인다.

아주 많은 단서를 주는 코드

(function () {
  const ua = jindo.$Agent().navigator();
  if (!(ua.ie && ua.version < 10)) {
    const sJavascriptFile = 'https://ssl.pstatic.net/sstatic/fe/sfe/ladder-game/ladders_230912.js';
    const fnCallback = function () {};
    nhn.common.load_js(sJavascriptFile, fnCallback, true, 150);
  }
})();

 

유저 에이전트 확인하는 코드는 건너뛰고, 누가 봐도 사다리게임 핵심 로직이 담긴 js 파일이 보인다. 파일명으로 보아하니 23년 9월 12일에 마지막으로 수정한 코드인 듯...?

 

전역에 선언된 nhn.common.load_js를 통해 해당 스크립트가 로드되는 방식인 것 같고... nhn.common.load_js에 들어가는 두 번째 인자는 빈 함수로 되어있는 걸 보니 onLoad 혹은 onFail, onError 콜백인 듯싶다.

 

세 번째 네 번째 인자는 뭔지 모르겠으니 nhn.common.load_js를 찾아보자.

 

콘솔창을 열어 nhn.common.load_js를 출력해 보니 다음과 같은 난독화된 함수가 나온다.

프론트는 이게 참 좋아...

뭔지 모를 저 텍스트를 더블클릭하면? 쨔잔

그래서 이게 뭔데...

load_js로 명명된 함수의 모습이 보인다.

사다리 게임 js 파일을 호출할 때에는 4개의 인자를 사용했는데, 로직을 보니 5개의 인자를 받을 수 있는 것 같다.

 

void 0 === r 은 r === undefined가 축약된 형태이다.

!0은 ture의 축약된 형태, !1은 false의 축약된 형태이다.

(우리의 코드를 1byte라도 아끼려는 처절한 노력)

 

위의 3줄은 인자에 대한 기본값 처리로 보이고, 우리가 신경 써야 할 부분은 첫 번째 t가 어떻게 처리되고 있는지에 대한 부분이다.

"string" == typeof t ? o ? r ? v(t, n) : d(t, n) : r || !h() ? v(t, n) : d(t, n) : o ? r ? v(void 0, n) : n() : r || !h() ? v(void 0, n) : n(), m = ""

 

... 정신이 나갈 것 같지만 정신을 붙들고 이럴 땐 chatgpt한테 if문으로 바꿔달라고 해보자

if (typeof t == 'string') {
  if (o) {
    if (r) {
      v(t, n);
    } else {
      d(t, n);
    }
  } else {
    if (r || !h()) {
      v(t, n);
    } else {
      d(t, n);
    }
  }
} else {
  if (o) {
    if (r) {
      v(undefined, n);
    } else {
      n();
    }
  } else {
    if (r || !h()) {
      v(undefined, n);
    } else {
      n();
    }
  }
}

 

브라우저 디버거에서 본 모습

브라우저 디버거에서 찍어본 input에 따라 실행 코드를 찾아보니 몇 줄 위의 함수가 실행되고 있었다.

스크립트를 지연 로드하는 코드로 보인다

 

결국 실행되는 구문은 window.require(["사다리 게임.js"], 빈 함수)라는 부분을 확인이 가능하다.

궁금해서 전역에 선언된 require 함수를 살펴보니 requirejs@2.3.5 구현체가 나왔다.

https://requirejs.org/

 

RequireJS

/* --- RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your c

requirejs.org

requirejs는 대략 10년 전에 활발히 사용되던 모듈 로드를 위한 라이브러리로, 의존성 처리와 비동기 모듈 로드를 통해 각 모듈의 성능 최적화를 돕는다.

 

window.require 호출을 통해 내가 원하는 사다리 로직을 주입해서 실행시킬 수 있을까? 검증을 위해 다음과 같이 테스트를 해보자.

 

1. 기존 캔버스 엘리먼트를 복사한다.

2. 기존 캔버스 엘리먼트를 삭제한다.

3. window.require(["사다리 게임.js"])를 콘솔에서 수동으로 트리거한다.

 

사다리 게임이 새 캔버스에서 정상적으로 동작하면 PoC는 성공이다!

 

 

 

대충 어떻게 돌아가는지, inject를 통해 기존 게임을 날리고 새로 시작할 수 있다는 사실도 알았으니 이제 사다리 게임.js을 살펴보고 내부 로직을 고쳐보자...

 

다음 편에서 계속.