자바스크립트 난독화 (Javascript Obfuscation)

출처: https://www.javatpoint.com/images/javascript/javascript_logo.png



코드 난독화(Code Obfuscation)

는 프로그램 코드의 일부 또는 전체를 변경하는 방법 중 하나로, 코드의 가독성을 낮춰 역공학에 대한 대비책을 제공합니다.
악성코드에 자주 쓰인다고 하죠.


test1~6까지, 심화되어가는 난독화 과정을 보여드리겠습니다.


  • test1.html
<script>
var n = 2;
var a = "test";
alert(a);
alert(n);
</script>
– 자바스크립트 문법의 소스이다. var로 자료형이 따로 없는 변수를 만들 수 있다. 그리고 alert()함수로 경고창을 띄우고 있다.
test1.html 실행 시에는






이렇게 두 개의 창이 뜨게 되는게 전부다.

그러나! 난독화를 한다면, 저렇게 간단한 소스가 인간의 눈으로는 해독이 거의 불가능한 상태까지 만들 수 있다.

다음을 보자.

  • test2.html
<script>var n = 2;var a = "test";alert(a);alert(n);</script>
– 보자마자, 몇 초동안은 속이 울렁거린다. 단순히 엔터를 없앤 것 뿐이다.
문장의 끝을 알리는 세미콜론(;)이 있기 때문에 저렇게 할 수 있는 것이다.
이렇게 엔터만 없애는 것으로 간단한 난독화가 이루어진다.  
(소스 코드 실행 결과는 동일하므로 이미지 생략)

다음을 보자.

  • test3.html
<script>var a = function() {var n = 2;var a = "test"; alert(a);alert(n);};a()</script>
– 조금 더 복잡해진 것 같다. 그러나 a라는 함수를 아까와 같을 일을 하도록 정의하여, 호출한다.
더 가보자.

  • test4.html
<script>a = '\<script\>';b = 'var a = fun';c = 'ction() {var n = 2;v';d = 'ar a = "test"; al';e = 'ert(a);alert(n);';f = '};a()\</script\>';document.write(a+b+c+d+e+f);</script>
– 와~! 뭔가 그럴듯하다. 머리는 더 아파온다. 이상한 문자들이 많아졌고, 자바스크립트의 문법도 맞지 않는 것 같다.

그러나 위 소스를 조금만 풀어보면…

<script>
a = '\<script\>';
b = 'var a = fun';
c = 'ction() {var n = 2;v';
d = 'ar a = "test"; al';
e = 'ert(a);alert(n);';
f = '};a()\</script\>';
document.write(a+b+c+d+e+f);
</script>
– 엔터만 몇 번 쳤을 뿐인데, 소스코드가 보기 쉬워진 것 같다. 잘 읽어보면 a, b, c … 변수를 선언하여, 위의 test3.html 파일의 내용을 모두 나누어 문자열로 만들었다.
# Without var, 변수는 지역변수가 된다.
그리고 a, b, c… 변수들을 모두 더해서 document.write()라는 함수의 인자로 전달하고 있다.
저 함수는 말그대로 문서(test4.html)에 [인자내용]을 써주는 것이다. a+b+c+… 을 해서 인자로 전달해주었기 때문에, 모든 문자열이 합쳐져서 test3.html과 동일한 HTML파일이 된 것이다.

다음을 보자.

  • test5.html
<script>a='\<scQPndAJslQmfsdWdr';b='ipQPndAJslQmfsdWdt\>vQPndAJslQmfsdWdar a = fQPndAJslQmfsdWdunQPndAJslQmfsdWd';c='ctQPndAJslQmfsdWdion() {vQPndAJslQmfsdWdarQPndAJslQmfsdWd n = 2;v';d='arQPndAJslQmfsdWd a = "test"; al';e='ert(QPndAJslQmfsdWda);aleQPndAJslQmfsdWdrt(n);';f='};a(QPndAJslQmfsdWd)\</sQPndAJslQmfsdWdcrQPndAJslQmfsdWdipt\>';str=a+b+c+d+e+f;document.write(str.replace(/QPndAJslQmfsdWd/g,''));</script>
<!--QPndAJslQmfsdWd!-->
– 이번엔 무슨 암호화라도 된 듯, 코드가 바뀌었다. 그리고 밑에 주석을 해주었는데… 무엇일까? (암호화 키?)

엔터좀 넣어보자…
<script>
a='\<scQPndAJslQmfsdWdr';
b='ipQPndAJslQmfsdWdt\>vQPndAJslQmfsdWdar a = fQPndAJslQmfsdWdunQPndAJslQmfsdWd';
c='ctQPndAJslQmfsdWdion() {vQPndAJslQmfsdWdarQPndAJslQmfsdWd n = 2;v';
d='arQPndAJslQmfsdWd a = "test"; al';
e='ert(QPndAJslQmfsdWda);aleQPndAJslQmfsdWdrt(n);';
f='};a(QPndAJslQmfsdWd)\</sQPndAJslQmfsdWdcrQPndAJslQmfsdWdipt\>';
str=a+b+c+d+e+f;
document.write(str.replace(/QPndAJslQmfsdWd/g,''));
</script>
<!--QPndAJslQmfsdWd!-->

코드를 잘보면 주석 내용이 소스(첫째줄)에 여러 개 포함되어 있다.
사실은 test4.html의 내용에 문자열 넣는 부분에 주석 내용을 닥치는대로 복붙한 것이다. (그러나 저 주석 내용이 변형되서는 안된다.)
저렇게 하니까 코드가 어려워져 보인 것이다.

“그런데, 저렇게 이상한 문자열을 넣은 코드를 실행하는데, 어떻게 아까랑 같은 결과가 나올까?”

비밀은  이 곳에 있다.
str=a+b+c+d+e+f;
document.write(str.replace(/QPndAJslQmfsdWd/g,''));
str이라는 변수에 모든 문자열(소스코드+주석내용)을 모두 더한 것을 대입하고, str.replace()라는 함수를 실행하였다.
replace는 말그대로, ‘대체하다’.
결국, QPndAJslQmfsdWd 이 문자열을 ”(빈칸)으로 바꾼 것이다. (/(슬래시)로 문자열을 구분, g(global)옵션으로 모든 문자열을 치환)
그러면, 저 이상한 주석내용이 다시 사라지겠죠?
따라서, test4.html과 같은 결과가 나온 것이다.

마지막을 보자.

  • test6.html
<script>a='%3c%73%63%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%72%69%70';b='%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%74%3e%76%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64';c='%61%72%20%61%20%3d%20%66%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%75%6e%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57';d='%64%63%74%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%69%6f%6e%28%29%20%7b%76%51%50%6e%64%41%4a%73%6c%51';e='%6d%66%73%64%57%64%61%72%51%50%6e%64%41%4a%73%6c%51%6d%66%73';f='%64%57%64%20%6e%20%3d%20%32%3b%76%61%72%51%50%6e%64%41%4a%73%6c%51';g='%6d%66%73%64%57%64%20%61%20%3d%20%22%74%65%73%74%22%3b%20';h='%61%6c%65%72%74%28%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%61%29%3b%61%6c%65%51';i='%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%72%74%28%6e%29';j='%3b%7d%3b%61%28%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%29%3c%2f%73%51%50';k='%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%63%72%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%69%70%74%3e';s1=a+b;s4=c+d;s5=e+f;s3=g+h;s2=i+j+k;str0=s1+s4+s5+s3+s2;str1=unescape(str0.replace(/%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64/g,''));document.write(str1);</script>
– 이제 문자열 내용은 눈으로 알아볼 수가 없다.

정리해보면…
<script>
a='%3c%73%63%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%72%69%70';
b='%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%74%3e%76%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64';
c='%61%72%20%61%20%3d%20%66%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%75%6e%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57';
d='%64%63%74%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%69%6f%6e%28%29%20%7b%76%51%50%6e%64%41%4a%73%6c%51';e='%6d%66%73%64%57%64%61%72%51%50%6e%64%41%4a%73%6c%51%6d%66%73';f='%64%57%64%20%6e%20%3d%20%32%3b%76%61%72%51%50%6e%64%41%4a%73%6c%51';
g='%6d%66%73%64%57%64%20%61%20%3d%20%22%74%65%73%74%22%3b%20';
h='%61%6c%65%72%74%28%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%61%29%3b%61%6c%65%51';
i='%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%72%74%28%6e%29';
j='%3b%7d%3b%61%28%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%29%3c%2f%73%51%50';
k='%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%63%72%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%69%70%74%3e';
s1=a+b;
s4=c+d;
s5=e+f;
s3=g+h;
s2=i+j+k;
str0=s1+s4+s5+s3+s2;
str1=unescape(str0.replace(/%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64/g,''));
document.write(str1);</script>
대충 보면, 뭔가 보이기는 하는데, 문자열 내용 때문에 알수가 없다.

여기서, 비밀은 ASCII Encoding을 했다는 것이다!

test5.html에서 test6.html로 업그레이드한 과정을 알아보자.

a=’\<scQPndAJslQmfsdWdr’; b=’ipQPndAJslQmfsdWdt\>vQPndAJslQmfsdWdar a = fQPndAJslQmfsdWdunQPndAJslQmfsdWd’; c=’ctQPndAJslQmfsdWdion() {vQPndAJslQmfsdWdarQPndAJslQmfsdWd n = 2;v’; d=’arQPndAJslQmfsdWd a = “test”; al’; e=’ert(QPndAJslQmfsdWda);aleQPndAJslQmfsdWdrt(n);’; f=’};a(QPndAJslQmfsdWd)\</sQPndAJslQmfsdWdcrQPndAJslQmfsdWdipt\>’; str=a+b+c+d+e+f;
위 코드를 크롬에서 F12를 눌러 Console에 입력해보면,
<scQPndAJslQmfsdWdripQPndAJslQmfsdWdt>vQPndAJslQmfsdWdar a = fQPndAJslQmfsdWdunQPndAJslQmfsdWdctQPndAJslQmfsdWdion() {vQPndAJslQmfsdWdarQPndAJslQmfsdWd n = 2;varQPndAJslQmfsdWd a = “test”; alert(QPndAJslQmfsdWda);aleQPndAJslQmfsdWdrt(n);};a(QPndAJslQmfsdWd)</sQPndAJslQmfsdWdcrQPndAJslQmfsdWdipt>
이렇게 조합되어서 나온다.
이 내용이 그 이상한 주석내용을 포함한 소스코드다.

밑에 사이트에서 위에 조합된 내용을 Hex로 ASCII Encoding 시켜보자.

그러면
3C 73 63 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 72 69 70 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 74 3E 76 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 61 72 20 61 20 3D 20 66 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 75 6E 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 63 74 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 69 6F 6E 28 29 20 7B 76 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 61 72 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 20 6E 20 3D 20 32 3B 76 61 72 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 20 61 20 3D 20 22 74 65 73 74 22 3B 20 61 6C 65 72 74 28 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 61 29 3B 61 6C 65 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 72 74 28 6E 29 3B 7D 3B 61 28 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 29 3C 2F 73 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 63 72 51 50 6E 64 41 4A 73 6C 51 6D 66 73 64 57 64 69 70 74 3E
이렇게 나오는데,
메모장에 들고가서 편집-바꾸기로 공백( )을 %로 바꿔주자. 그러면
3C%73%63%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%72%69%70%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%74%3E%76%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%61%72%20%61%20%3D%20%66%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%75%6E%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%63%74%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%69%6F%6E%28%29%20%7B%76%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%61%72%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%20%6E%20%3D%20%32%3B%76%61%72%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%20%61%20%3D%20%22%74%65%73%74%22%3B%20%61%6C%65%72%74%28%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%61%29%3B%61%6C%65%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%72%74%28%6E%29%3B%7D%3B%61%28%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%29%3C%2F%73%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%63%72%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%69%70%74%3E
이렇게 나올텐데, 제일 앞에 3C까지 %를 붙혀주면,
%3C%73%63%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%72%69%70%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%74%3E%76%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%61%72%20%61%20%3D%20%66%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%75%6E%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%63%74%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%69%6F%6E%28%29%20%7B%76%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%61%72%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%20%6E%20%3D%20%32%3B%76%61%72%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%20%61%20%3D%20%22%74%65%73%74%22%3B%20%61%6C%65%72%74%28%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%61%29%3B%61%6C%65%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%72%74%28%6E%29%3B%7D%3B%61%28%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%29%3C%2F%73%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%63%72%51%50%6E%64%41%4A%73%6C%51%6D%66%73%64%57%64%69%70%74%3E
‘이상한 주석내용‘과 함께 ‘ASCII Encoding’된 소스코드가 완성된다.

이제 test4.html처럼 어려워보이게 문자열로 만들어 나눠주면,
a=’%3c%73%63%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%72%69%70′;
b=’%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%74%3e%76%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64′;
c=’%61%72%20%61%20%3d%20%66%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%75%6e%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57′;
d=’%64%63%74%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%69%6f%6e%28%29%20%7b%76%51%50%6e%64%41%4a%73%6c%51′;e=’%6d%66%73%64%57%64%61%72%51%50%6e%64%41%4a%73%6c%51%6d%66%73′;f=’%64%57%64%20%6e%20%3d%20%32%3b%76%61%72%51%50%6e%64%41%4a%73%6c%51′;
g=’%6d%66%73%64%57%64%20%61%20%3d%20%22%74%65%73%74%22%3b%20′;
h=’%61%6c%65%72%74%28%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%61%29%3b%61%6c%65%51′;
i=’%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%72%74%28%6e%29′;
j=’%3b%7d%3b%61%28%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%29%3c%2f%73%51%50′;
k=’%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%63%72%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64%69%70%74%3e’;
이런식으로 되고(대소문자 구분X), 마지막으로 복잡해보이게 조합시켜주면
s1=a+b;
s4=c+d;
s5=e+f;
s3=g+h;
s2=i+j+k;
str0=s1+s4+s5+s3+s2;
완성됐다.

이제 마지막으로 코드 실행을 위해 해줄 일은, 주석에 달렸던 ‘이상한 문자열’을 빈칸(”)으로 바꾸고, ASCII Decoding 한 후 document.write() 해주면 된다.(순서가 바뀌어도 된다)

그래서
str1=unescape(str0.replace(/%51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64/g,”));
가 있었던 것이다.
당연히 %51%50%6e%64%41%4a%73%6c%51%6d%66%73%64%57%64는 그 ‘이상한 문자열’ 일 것이고, 빈칸(”)으로 바뀔 것이다.
그 다음에 감싸고 있는 함수는 unescape()이다. 디코딩을 해주는 녀석이다.
그리고 document.write(str1); 가 실행되며

(크롬 F12 Console에서 확인해보면)
최종적으로 <script>var a = function() {var n = 2;var a = “test”; alert(a);alert(n);};a()</script>라는 문자열로 Document에 들어가게 된다.

이제 test6.html을 이해하실 수 있으시겠죠?

실제로 이런 난독화된 코드를 해독하는 전문가분들은 1시간정도 걸린다고 합니다.
존경스럽네요! 저도 꼭 제가 원하는 분야의 전문가가 되어 존경받는 사람이 되었으면 합니다.
Actually, experts decoding obfuscated code take about 1 hour doing like this.
I admire them. I want to be like them in the future through my professional skill.

댓글