본문으로 바로가기

1. 개요

JavaScript Garden은 자바스크립트 언어의 핵심적인 내용을 모아 놓은 웹 문서이다. 이 문서는 자바스크립트 언어 자체를 설명하지 않고, 이 언어를 익히고, 사용하는데 있어 자주 겪는 실수, 미묘한 버그, 성능 이슈, 나쁜 습관 등 자바스크립트 언어의 독특한 특징들을 설명하고 있다.

2. 난독화 관련 내용

JavaScript Garden 내용을 기반으로 자바스크립트 난독화에서 이해하기 어려웠던 언어의 특징들을 잘 설명하고 있어 공부하는데 많은 도움이 되었다.

2.1. 객체 > 객체와 프로퍼티 > 프로퍼티 접근

객체이름 다음에 점을 찍어(Dot Notation) 접근하거나 각괄호를 이용해(Square Bracket Notation, []) 접근할 수 있다. 두 방식 모두 거의 동일하게 동작한다. 다만 차이가 있다면 각괄호 방식은 프로퍼티 이름을 동적으로 할당해서 값에 접근 할수 있지만 점을 이용한 방식은 구문 오류를 발생시킨다.

var foo = {name: 'kitten'}
foo.name; // kitten
foo['name']; // kitten

var get = 'name';
foo[get]; // kitten

foo.1234; // SyntaxError
foo['1234']; // works

document.write()함수는 document["write"] 가 가능하고 다시 window["document"]["write"] 가 가능하다. 각괄호 안의 값은 문자열이기 때문에 인코딩 형태로 변형할 수 있다.

그림 1. 16진수 난독화

2.2. 함수 > 함수 선언과 함수 표현식 > 표현식

자바스크립트에서 함수는 First Class Object다. 즉, 함수 자체가 또 다른 함수의 인자가 될 수 있다는 말이다.

var foo = function() {}

그래서 기존에 정의된 함수를 다른 변수로 대체 가능하다. 예를 들면 난독화에서 함수명을 쉽게 읽히지 않도록 하기위해 djEfUz = eval 형태로 변경 가능하다. 다음 소스코드는 공다팩의 일부이다.

그림 2. 공다팩 소스코드 중

2.3. 함수 > 스코프와 네임스페이스

자바스크립트는 '{ }' Block이 배배 꼬여 있어도 문법적으로는 잘 처리하지만, Block Scope은 지원하지 않는다. 그래서 JavaScript에서는 항상 함수 스코프를 사용한다. 난독화에서는 이러한 특징을 이용하여 자바스크립트 압축을 구현할 수 있다.

자바스크립트 컴프레셔는 자바스크립트 압축기술로 YUI Compressor 같은 도구들이 존재한다. 압축기를 사용하는 이유는 다운로드 받는 자바스크립트 파일의 용량을 줄이면, 사용자 입장에서는 빠르게 받고 실행할 수 있어 브라우징 속도가 빨라지고, 서버 입장에서는 트래픽 소비량을 줄여 비용을 절감할 수 있다.

var is = {
    ie:      navigator.appName == 'Microsoft Internet Explorer',
    java:    navigator.javaEnabled(),
    ns:      navigator.appName == 'Netscape',
    ua:      navigator.userAgent.toLowerCase(),
    version: parseFloat(navigator.appVersion.substr(21)) ||
             parseFloat(navigator.appVersion),
    win:     navigator.platform == 'Win32'
}
is.mac = is.ua.indexOf('mac') >= 0;
if (is.ua.indexOf('opera') >= 0) {
    is.ie = is.ns = false;
    is.opera = true;
}
if (is.ua.indexOf('gecko') >= 0) {
    is.ie = is.ns = false;
    is.gecko = true;
}

이 코드를 압축시키면 대부분 이런형태로 생성된다.

var is={ie:navigator.appName=='Microsoft Internet Explorer',java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',ua:navigator.userAgent.toLowerCase(),version:parseFloat(navigator.appVersion.substr(21))||parseFloatnavigator.appVersion),win:navigator.platform=='Win32'}is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;}if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}

2.4. 타입 > 객체 비교하기 > 타입 캐스팅

자바스크립트는 Weak Typing 언어이기 때문에 필요할 때마다 알아서 타입을 변환한다.

Strong / Weak Typing 은 타입에 제약이 많을 수록 Strong, 적어질 수록 Weak에 가까워진다. 예를 들면 var a = 3 으로 했을 때 정수형으로 자동 형 변환을 해주는 형태가 Weak Type, 정확하게 int a = 3 으로 선언해야 정수형으로 저장되는 경우가 Strong Type 으로 이해하면 된다. 자바스크립트는 Weak Typing을 따르기 때문에 자동 형 변환을 해주며, 이런 변환을 타입 캐스팅이라 부른다.

그래서 [] == 0 비교를 하면 true로 출력되는데, 이 때 [] 객체가 0과 비교에서 숫자 타입으로 타입 캐스팅된다. 이중 등호 연산자(==)는 두 객체의 자료형을 강제로 변환하기 때문에 비교가 가능하고, []이 변환되었을 경우 0을 가지기 때문에 true가 출력된다.

이와 같은 맥락으로 ~ 연산이나 + 연산 같이 숫자를 대상으로 연산이 포함될 경우 타입 캐스팅에 의해 강제 형 변환되어 연산이 진행되며 jjencode에서 사용된 ~[]-1이라는 값을 가지게 되는 이유이다.

그림 3. jjencode

그림 4. [ ] 오브젝트와 타입 캐스팅 테스트

2.5. 핵심 > 왜 eval을 사용하면 안 될까?

eval() 함수는 자바스크립트 문자열을 지역 스코프에서 실행한다.

var number = 1;
function test() {
    var number = 2;
    eval('number = 3');
    return number;
}
test(); // 3
number; // 1

eval() 함수는 eval() 이라는 이름으로 직접 실행할 때만 지역 스포크에서 실행된다.

var number = 1;
function test() {
    var number = 2;
    var copyOfEval = eval;
    copyOfEval('number = 3');
    return number;
}
test(); // 2
number; // 3

어쨌든 eval()은 사용하지 말아야 한다. eval()을 사용하는 99.9%는 사실 eval() 없이도 만들수있다. eval()은 어떤 코드라도 무조건 실행하기 때문에 보안 문제도 있다. 따라서 신뢰하지 못하거나 모르는 코드가 포함되어 있을 경우 절대로 사용해서는 안된다. 어쨋든 eval()은 사용하지 않는게 좋다. eval()을 사용하는 모든 코드는 성능, 보안,버그 문제를 일으킬 수 있다. 만약 eval()이 필요해지면 설계를 변경해서라도 eval()이 필요 없게 만들어야 한다.

전역과 지역 스코프에서 사용될 때 보안적 이슈와 차이점에 대해 자세한 설명이 없어 이해하기가 어렵다. 하지만 자바스크립트 난독화에서 흔히 사용하는 함수가 eval() 인것은 확실하고, eval() 함수를 사용했을 경우 안의 내용을 무조건적으로 실행하기 때문에 많은 문제를 가지고 있는 것은 확실하다.

2.6. 핵심 > undefined와 null > undefined도 변수

undefinedundefined라는 값을 가지는 데이터 형식이다. undefined는 상수도 아니고 자바스크립트의 키워드도 아니다. 그냥 undefined라는 이름의 Global 변수이고 이 변수에는 undefined라고 할당돼 있다. 그래서 이 Global 변수의 값을 쉽게 바꿀 수 있다.

같은 맥락으로 0/0으로 출력되는 NaN이나 123/0으로 출력되는 Infinity도 동일하다. 이렇게 출력되는 값을 다시 재활용 가능한데, 이러한 형태를 jjencode에서도 볼 수 있다.

그림 6. ECMAScript 6th 내용

타입 캐스팅에 의존하지 않고 정확한 형을 지정하기 위해 사용한 방법 중에 +"" 또는 ""+ 가 있다. (더블 쿼터 대신 싱글 쿼터를 써도 무방하다.) + 연산자 옆에 쓰여질 피연산자를 문자열로 자동 형 변환을 시켜준다. 이렇게 형 변환된 데이터를 ()로 감싼다. 괄호로 감싸는 이유는 연산자 우선순위를 두기 위해서이다.

그림 7. 선언된 글로벌 변수 변형

2.7. 보이지 않게 사용되는 eval 함수

setTimeout() 함수와 setInterval() 첫 파라미터는 실행가능한 문자열을 넘길 수 있다. 하지만 이 실행은 내부적으로 eval()을 사용하는 것이기 때문에 두 함수를 사용하지 않는 것이 좋다.

그림 8. setTimeout의 첫 파라미터 테스트

3. 참조 사이트


댓글을 달아 주세요

티스토리 툴바