난독화

코드 난독화는 프로그래밍 언어로 작성된 코드에 대해 읽기 어렵게 만드는 작업이다. 대표적인 사용 예로는 프로그램에서 사용된 아이디어나 알고리즘 등을 숨기는 것 등이 있다. 코드 난독화 과정을 거친 코드를 특정 기준에 의해 심사하는 대회도 있다.

개요 
코드 난독화는 프로그램 코드의 일부 또는 전체를 변경하는 방법 중 하나로, 코드의 가독성을 낮춰 역공학에 대한 대비책을 제공한다. 난독화를 적용하는 범위에 따라 소스 코드 난독화와 바이너리 난독화로 나눌 수 있다. 또한, 난독화의 목적에 따라 각각 기술의 무단복제와 불법으로 침입하려는 프로그램을 방지하는 것으로 나뉘기도 한다.

원리 

 필요 이상으로 복잡한, 또는 아무것도 하지 않는 코드를 작성한다.
 관련이 없는 여러 함수들을 뒤섞는다.
 데이터를 알아보기 힘들게 인코딩한다.

만드는 방법 
다음과 같은 소수인지를 판별하는 액션스크립트 3 소스에 있는 isPrime 함수를 난독화 시켜보자.
//난독화 할 함수.
function isPrime(n:int):Boolean{
	var r:int = 0;
	while(r*r<=n)r++;
	r--;//r은 [n의 제곱근]임.
	if(n==r) return false;//n이 0 또는 1
	if(n<4) return true;
	if(n%2==0 || n%3==0 || n%r==0){
		return false;
	}
	for(var i:int = 6;i<r;i+=6){
		if(n%(i-1)==0) return n==i-1;
		if(n%(i+1)==0) return n==i+1;
	}
	return true;
}

//함수 테스트
var num:int = 0;
for(var i:int=0;i<=100000;i++){
	if(isPrime(i)) num++;
}
trace(num);//9592(π(100000))이 출력된다.

for를 while로 바꾸고, 특별한 변수를 쓴다. 
 for 문을 while 문으로 바꾼다.
 만약 for 문이 두 번 이상 중첩되었을 경우, 아래와 같이 특별한 변수를 쓴다.
int i,j;
for(i=0;i<9;i++)
for(j=0;j<9;j++)
printf("%d*%d=%d\n",i+1,j+1,(i+1)*(j+1));
위 C 코드를
int k=0;
int i(0),j(0);
while(k<9*9){
	i=k/9;j=k%9;
	printf("%d*%d=%d\n",i+1,j+1,(i+1)*(j+1));
	k++;
}
이렇게 바꾼다.

그 결과, 첫 번째의 액션스크립트 코드는 다음과 같이 바뀐다. (함수 부분만)
function isPrime(n:int):Boolean{
	var r:int = 0;
	while(r*r<=n)r++;
	r--;
	if(n==r) return false;
	if(n<4) return true;
	if(n%2==0 || n%3==0 || n%r==0){
		return false;
	}
	var i:int=6;
	while(i<r){
		if(n%(i-1)==0) return n==i-1;
		if(n%(i+1)==0) return n==i+1;
		i+=6;
	}
	return true;
}

순환문을 재귀 함수로 바꾼다. 
모든 순환문은 재귀 함수로 바꿀 수 있다. 함수의 인자로 함수 내부에 쓰이는 변수를 추가하고, 재귀적으로 호출을 시키는 방식이다.

while문의 조건을 if문으로 바꾸는 식으로 순환문을 다음과 같이 재귀 함수로 바꿀 수 있다.
function isPrime(n:int, r:int=0, i:int=6):Boolean{
	if((r+1)*(r+1)<=n) return isPrime(n, r+1, i);
	else if(n==r) return false;
	else if(n<4) return true;
	else if(n%2==0 || n%3==0 || n%r==0) return false;
	else if(i<r)
		if(n%(i-1)==0) return n==i-1;
		else if(n%(i+1)==0) return n==i+1;
		else return  isPrime(n, r, i+6);
	else return true;
}
이 방식에서 생기는 문제는, 수많은 함수 호출로 인해 속도가 매우 느려진다는 것이다.

구조를 난독화하고, 변수 이름을 의미없게 짓는다. 
if(A) B; else if(C) D; else E;
같은 if-else 문을 ?: 조건 연산자를 이용하여
A?B:C?D:E;
로 바꿀 수 있다.

변수 이름을 무작위로 (예: int rabbitNum = 0;을 int m = 0;으로 바꿈) 지어서 코드를 더욱 난독화 시킬 수 있다.
function isPrime(a:int, b:int=0, c:int=6):Boolean{
	return (b+1)*(b+1)<=a?isPrime(a, b+1, c):
	a==b?false:
	a<4?true:
	a%2==0 || a%3==0 || a%b==0?false:
	c<b?a%(c-1)==0?a==c-1:a%(c+1)==0?a==c+1:isPrime(a, b, c+6):true;
}

내부 변수를 없앤다. 
만약 내부 변수가 쓰인다면, 그것을 다른 것으로 치환한다.

예 : 다음 C 코드를 다음과 같이 바꾼다.
double divide(int a, int b){
double c = a/(double)b;
return c;
}
c를 a/(double)b로 치환한다.
double divide(int a, int b){
return a/(double)b;
}

이름을 다시 한번 난독화한다. 
함수 이름과 변수 이름을 바꾼다. 여기에서는 변수 이름을 _, __, ___으로 바꾸고, 함수 이름을 으로 바꿨다.
function (_:int, __:int=0, ___:int=6):Boolean{
	return (__+1)*(__+1)<=_?(_, __+1, ___):
	_==__?false:
	_<4?true:
	_%2==0 || _%3==0 || _%__==0?false:
	___<__?_%(___-1)==0?_==___-1:_%(___+1)==0?_==___+1:(_, __, ___+6):true;
}

리터럴을 없앤다. 
리터럴을 함수 인자로 넘겨주는 방법을 쓸 수도 있고, 살짝 위험하기는 하지만, 1을 a/a와 같이 다른 변수로 치환시키는 방법을 쓸 수도 있다.

비록 권장하는 방법은 아니지만, 여기에서는 후자의 방법을 쓰겠다.
function (_:int, __:int=0, ___:int=6):Boolean{
	return (__+___/___)*(__+___/___)<=_?(_, __+___/___, ___):
	_==__?_-_:
	_<___-(__+_+__-_)/__?_/_:
	_%(__/__+_/_)==_-_ || _%(__*___/(__+__))==__-__ || _%__==___-___?___/___-_/_:
	___<__?_%(___-_/_)==__/__-___/___?_==___-__/__:_%(___+___/___)==__+_-__-_?
	_==___+___/___:(_, __, ___+(__*___*(_+_+_)+__*_*___+(__+__)*___*_)/_/__/___):_;
}

스페이스, 탭 등을 없앤다. 
여기에서는 한 줄로 적으면 너무 길어지므로, 적당히 줄바꿈을 하였다.
function (_:int,__:int=0,___:int=6):Boolean{return(__+___/___)*(__+___/___)<=_?(_,__+___/___,___):_==__?_-_:
_<___-(__+_+__-_)/__?_/_:_%(__/__+_/_)==_-_||_%(__*___/(__+__))==__-__||_%__==___-___?___/___-_/_:___<__?
_%(___-_/_)==__/__-___/___?_==___-__/__:_%(___+___/___)==__+_-__-_?_==___+___/___:
(_,__,___+(__*___*(_+_+_)+__*_*___+(__+__)*___*_)/_/__/___):_}

최종 결과 
function (_:int,__:int=0,___:int=6):Boolean{return(__+___/___)*(__+___/___)<=_?(_,__+___/___,___):_==__?_-_:
_<___-(__+_+__-_)/__?_/_:_%(__/__+_/_)==_-_||_%(__*___/(__+__))==__-__||_%__==___-___?___/___-_/_:___<__?
_%(___-_/_)==__/__-___/___?_==___-__/__:_%(___+___/___)==__+_-__-_?_==___+___/___:
(_,__,___+(__*___*(_+_+_)+__*_*___+(__+__)*___*_)/_/__/___):_}

//함수 테스트
var num:int = 0;
for(var i:int=0;i<=100000;i++){
	if((i))num++;
}
trace(num);//9592

사용 예 
 자바스크립트 코드의 용량을 줄이는 데 쓰는 Packer는, 또한 자바스크립트를 읽기 어렵게 하는 데 쓰인다.

각주

외부 링크 
  International Obfuscated C Code Contest

 
보안
컴퓨터 프로그래밍
프로그램 변환
안티패턴