내 월급 내가 보겠다는데 이리 힘들 줄이야

전말

짜증의 시작

글쓰는게 미숙하여 글마다 어투가 다르지만 이번글은 일상이기 때문에 구어체로 작성된다.

스타트업에 다니는 나는 회사에서 따로 회계사를 두지 않아 외부 회계사로부터 급여명세서를 받아본다. 사건의 시작은 평소와는 다른 이상하리 만큼 빠른 시기에 급상여명세서가 빨리 온 4월의 어느 월급날 전에 벌어졌다.

급여명세서나 카드 이용내역서는 생년월일을 비밀번호로 하여 암호화 되어 html파일로 즉 기본적인 웹 기반으로 되어 있다는 것이다.
마찬가지로 첨부되어 있는 파일을 여니 생년월일이 비밀번호로 되어 있다는 문구와 함꺠 하나의 페이지가 열렸고 생년월일을 입력하였지만 비밀번호는 맞지 않다는 문구만 볼 수 있었다.

파일이 잘못됬다는 3번의 문의와 다시 만들어 보낸다는 3변의 답장을 받았지만 열수 없다는 반복되는 화면만 보게 되는 상황이 짜증을 불러오고 결국 내가 직접 열기를 시도했고 그 과정을 기록했다.

알아보자

앞서 말한대로 파일은 html 단일 파일로 되어있었다.
헤더부분에는 스타일 스크립트와 자바스크립트 코드들이 있는 것을 볼 수 있었고, 암호화된 코드로 보이는 것은 hidden타입의 input 태그에 담겨져 있다.
열어보면 역시 당연하게도 파일을 열어보면 여타 다른 html와 같은 구조를 가지고 있고, 특징이 있다면 난독화는 되어있지 않고 개행문자 정도만 제거되어 있는 minify 정도만 되어았다는 점이다.
덕분에? 나는 내 월급을 열어볼 수 있었다.

흔하디 흔한 pretty view 사이트를 통해 복호화 코드를 얻을 수 있었다.

암보 비교 구문


  if (Math.abs(pwd << pwd) != unescape('\x31\x37\x34\x34\x38\x33\x30\x34\x36\x34')) {
      alert('비밀번호가 일치하지 않습니다.');
      return;
  }

처음 이 코드를 봤을 때 Math.abs(pwd << pwd) 이 부분이 이해가 잘 안 됬다.
물논 암호를 암호만큼 쉬프트 연산을 한 다음 절대값을 취한 다는 것은 알지만 이것이 무슨 의미를 가지는 지는 모르겠다.
위의 숫자를 다음 1744830464와 비교한다.

unescape(‘\x31\x37\x34\x34\x38\x33\x30\x34\x36\x34’)

난독화도 안해 놓고 이런식으로 넣을 것이라면 왜 escape된 문자열로 넣는지 모르겠다. 이정도로 코드를 뜯어볼 개발자라면 저정도는 해석이 될텐데..
그냥 1744830464를 넣어누지;

Math.abs(pwd « pwd)

거~~~~~~~~~~~대한 비트연산인 만큼 잘려나가는 비트들이 생길테고 마지막에 절대값 처리까지한다.
즉, 연산결과가 1744830464가 나오는 암호값이 유일한 값이 아닐 수 있다고 생각했다.

for(let pwd = 0 ; pwd<999999; pwd++){
  if(Math.abs(pwd << pwd) === 1744830464){
    console.log(pwd)
  }
}

역시 단순 무식하게 돌려보면 비밀번호로 사용 가능한 어마무시한 숫자들이 나오게 된다.

물론 이중에 암호화 정보를 복화화 할 수 있는 키는 하나 뿐이다.

복호화 구문


  var bin = unescape(cliperText).split(',');
  var text = '';
  var strKey = pwd;
  for (var i = 0; i < bin.length; i++) {
      text = text + String.fromCharCode(Number(bin[i]) + strKey.charCodeAt(i % strKey.length));
  }
  document.write(text);

var bin = unescape(cliperText).split(‘,’);

금여명세서 파일에 담겨있는 암호화된 문자열을 대입하여 실행해보면 숫자로된 배열을 얻을 수 있다.

bin = [“3”, “52”, “67”, “59”, “60”, “6”, “3”, “52”, “52”, “47”, “52”, “6”, “-25”, “8”, “67”, “55”, “68”, “52”, “44”, “10”, “9”, “8”, “-16”, “44497”, “49288”, “50616”, …

아래 한줄이 복호화의 모든 과정이 담긴 코드이다.

text = text + String.fromCharCode(Number(bin[i]) + strKey.charCodeAt(i % strKey.length));

눈 여겨 봐야 할 코드는 String.fromCharCodecharCodeAt 이란 두가지 함수 이다. 두가지 함수는 ascii코드를 문자로, 해당 위치의 문자(char)를 아스키 코드로 변환하여 변환하는 역활을 한다.

위의 코드를 해석하면 이러한 과정을 반복하여 복호화를 한다.

  1. 암호6자리를 순서대로 반복하여 아스키 코드로 변환하여 키값으로 사용한다.
  2. 암호화된 문자열(위의 bin 변수)의 값과 키값을 합친다.
  3. 합친 값을 다시 문자열로 바꿔 저장한다.
  • 끝나게 되면 복호화된 문자열을 document.write(text);를 통하여 화면에 뿌려주게 된다.

해독하기

위의 두가지 과정을 통해 알 수 있었것을 통해 알 수 있는 생각보다 별로 없었다.

암호를 6개의 숫자를 돌아가며 키값으로 쓰고(6개 단위로 반복되는 패턴) 있다는 것이다.

영화 이미테이션 게임에 보면 암호무전에 매번 같은 문자를 시작으로 하는 문자를 보고 암호를 푸는 장면이 나오게 된다. 결과적으로는 나도 그런식으로 암호를 풀게 되었다.

1,2,3월달의 암호를 치고 들어갔을 경우의 값(복호화된 결과값)을 비교했다.
html 문서인 만큼 시작값은 언제나 <html> 이였다.

….. 다 풀없다.

복호화 하였을 경우 처음 6글자가 <html>인 6개의 숫자만 대입하여 찾으면 되는 것이다. 암호가 될수 있는 값들 중에 100000부터 시작했을 경우 처음 후보인 100058일 경우 값은

4dskq>4dd_i>8sgyl....

벌써 하나가 풀리기 시작했다. 처음 6자리가 4dskq> 인것을 봤을 때 <html>을 비교해봤을 때 비밀번호 마지막 자리는 8일 것이다.
몇번 반복하고 난뒤 금방 비밀번호를 알 수 있었고 비밀번호는 941208이였다.

누구냐 넌…

941208로 풀어 보면 예상대로 <html><head> <titl로 시작하는 문자열을 얻을 수 있었고 뒤의 문자도 깔끔하게 복호화 됨을 볼 수 있었다.

남들은 자신의 생년월일 6숫자치고 보는걸 난 본의아니게 이렇게 보게 되었다.
끝.

후기

내 월급 내가 보겠다는게 겁나 힘드네