2개 이상의 서로 다른 바이너리 분석해 취약점 진단 가능  

이 방법으로 엔씨소프트에서 사용하는 카뮤즈 프로그램 취약점 발견 


[보안뉴스=이승진 GrayHash 수석] 최근 PC 사용자들이 대부분 사용하는 각종 소프트웨어의 제로데이 취약점을 악용한 보안위협이 끊임없이 발생하고 있다. 이에 여기에서는 ‘바이너리 디핑(Binary Diffing)’이라는 기술을 활용해 제로데이 취약점을 찾을 수 있는 기법에 대해 살펴보도록 한다. 


정보보안 분야에서는 2개 이상의 서로 다른 Binary를 비교하여 분석하는 기술을 Binary Diffing이라고 부른다. Binary Diffing 기술이 실제 언더그라운드 해커들 사이에 알려진 것은 대략 10년 전부터로 지금까지 꾸준히 연구되고 있는 분야라고 할 수 있다. 이 기술은 정보보안 분야의 다양한 이슈를 해결하기 위해 사용되고 있는데, 잘 알려진  Binary Diffing 기술의 활용분야는 다음과 같다.


1. Patch Analysis

Binary Diffing은 보안 패치가 적용되지 않은 Binary와 적용된 Binary와의 차이점을 비교(Diffing)해 이전 버전과 비교했을 때 어떤 점이 달라졌는지 알아내는 목적에 사용될 수 있다. 가령, 마이크로소프트(MS)는 주기적으로 보안 패치를 실시하는데 패치된 Binary와 그렇지 않은 Binary를 비교함으로써, 보안 문제점의 원인이 무엇인지 빠르고 정확하게 알 수 있다. 이렇듯 이 방법은 패치 분석을 위해 보안전문가들에게 많이 활용된다고 볼 수 있다.


2. Identifying Symbols, Platform-independent Difference Analysis

리버스 엔지니어(Reverse Engineer)들에게 많이 활용되는 분야로, Binary Diffing 기술을 이용하면 스트립(Strip)된 Binary의 함수 정보를 알아낼 수 있다. 또한, 서로 다른 아키텍처에서 컴파일된 Binary를 비교 분석해 유용한 정보를 도출해내는 것도 가능한데 대표적으로 ARM용으로 컴파일된 Binary와 x86용으로 컴파일된 Binary를 비교해볼 수 있다. 즉, 많은 시간을 들이지 않아도 디핑(Diffing)을 통해 유용한 정보를 비교적 손쉽게 알아낼 수 있는데 이 경우 리버싱(Reversing) 작업시간을 획기적으로 단축시킬 수 있다.


3. Automatic Malware Detection

다양한 변종들로 인해 악성코드는 하루에도 수십 만개 이상이 쏟아져 나오고 있다. 그러나 사실 이 변종들은 비슷한 알고리즘을 쓰는 경우가 많다. 가령, 샘플의 개수가 수백 만개여도 핵심 알고리즘은 같을 수 있다는 얘기다. 여기서 Binary Diffing 기술을 활용할 경우, 알고리즘 비교를 통해 바이러스들을 분류할 수 있다. 이러한 기능을 수행하는 제품은 현재는 구글에 인수된 Zynamics 사의 VxClass가 있다.


4. License Check

오픈소스 라이선스 위반은 대기업들에게도 골치가 아픈 문제다. 의도적으로 라이선스를 위반하려는 것이 아니라, 개발시 사용한 라이선스에 대한 표기를 누락하는 경우가 발생할 수도 있기 때문이다. 일부 기업의 경우 라이선스 위반을 검사하는 프로그램 개발을 준비 중인데, 일반적으로 이런 경우 개발팀 소속이 아니기 때문에 소스코드에 접근할 수 없다. 즉, Binary 레벨에서 목적을 달성해야 하는데 Diffing 기술을 이용하면 Binary 레벨에서 오픈소스 라이선스 코드를 검출해낼 수 있다.


앞서 설명한 것은 Binary Diffing 기술이 기존에 활용된 케이스이며, 다음에는 Diffing 기술을 활용해 소프트웨어 취약점을 발견해내는 방법에 관한 것이다. 기본 원리는 다음과 같이 단순하다.


“취약한 함수의 패턴을 대상 Binary에 비교하여 유사도를 평가한 후 취약성 존재여부를 판단”


즉, 취약점이 발생할 수 있는 유형의 패턴이나 취약점이 존재한다고 증명된 함수를, 취약점을 찾을 Binary와 비교하는 방식을 통해 취약점을 찾아내자는 것이다. 기본적인 아이디어는 단순하지만 실제 제대로 작동하기 위해서는 해결해야 하는 많은 문제점이 존재한다. 먼저, 이해를 돕기 위해 Binary가 아닌 소스코드 레벨에서부터 예를 들어보겠다. 다음 2개의 소스를 비교해보도록 한다.


- func_1


1: void vuln_sample1(char *str) {

2:   char buf[256];

3:   strcpy(buf, str);

4:   printf(“%s\n”, buf);

5: }


- func_2


1: void vuln_sample3(char *str) {

2:    char buf[256];

3:    printf("test\n"); // <-- added

4:    strcpy(buf, str);

5:    printf("%s\n", buf);

6: }


위 2개의 함수들에는 strcpy() 함수의 오용으로 인해 전형적인 스택 오버플로우(Stack Overflow) 취약점이 존재한다. func_2의 3번째 라인에는 func_1에는 없는 printf 함수가 추가되어 있다. 함수 하나만 추가됐기 때문에 두 함수를 컴파일하여 비교해보면 크게 다르지 않다.


실제로 Binary Diffing 프로그램 중 보안업계에서 가장 많이 쓰이는 Zynamics 사의 BinDiff를 이용하여 실행하면 유사도 평가 결과값이 높다. 즉, 만약 우리가 func_1에 대한 패턴을 확보하고 있다면, 소스코드가 조금은 다른 형태지만 취약점이 발생하는 부분은 같다고 할 수 있는 func_2를 취약하다고 판단할 수 있다는 것이다. 그러나 아쉽게도 다음 예제 소스와 같은 경우에는 조금 다른 결과가 나온다.


- func_3


1: void vuln_sample2(char *str) {

2:    char buf[256];

3:    char *p; // // <-- added

4:    printf("this is func_3\n"); // <-- added

5:    strcpy(buf, str);

6:    p=strstr(buf, "beist\n"); // <-- added

7:    if(p) // <-- added

8:       printf("beer\n"); // <-- added

9:    else // <-- added

10:      printf("crying\n"); // <-- added

11:}


func_3는 func_1과 비교했을 때 5라인이 더 추가됐지만 버그 헌터의 관점에서 봤을 땐, ‘의미적으로는’ 여전히 func_1과 동일한 취약점이 존재한다고 할 수 있다. 그렇지만 BinDiff를 이용하여 func_1과 func_3를 Diffing 해보면 유사도 평가에서 결과값이 굉장히 낮다. 즉, BinDiff는 이 두 개의 함수에 대해서 ‘차이가 많이 난다’고 말하고 있는 셈이다.


왜 이러한 결과값이 나오는지 알기 위해서 Binary Diffing에 사용되는 알고리즘을 간략히 알아보자. Binary Diffing 프로그램들의 알고리즘은 조금씩 다르지만 전체적인 개념은 같다고 할 수 있다. 여기에서는 대표적인 툴인 BinDiff의 알고리즘에 대해 다루어본다.


BinDiff는 서로 다른 2개의 함수에 대해서 차이점을 찾기 위한 알고리즘을 수행한다. 각 함수의 CFG(Control Flow Graphs)를 기반으로 차이점을 평가하는데, CFG는 CG(Call Graphs)에 비하여 세부적으로 표현한다고 이해할 수 있다. Call Graphs는 함수와 함수간의 연결 관계를 표현한다면 CFG는 하나의 함수 안에 있는 기본 블록(Basic Block) 간의 연결 관계까지 표현하기 때문이다.


즉, BinDiff는 두 함수에서 사용된 어셈블리 명령(Assembly Instruction)의 차이 그 자체에 대해서는 크게 의미를 두지 않고, 구조적 매칭(Structural Matching)을 시도하고 있다. BinDiff는 먼저 함수들 간에 비교한 후 기본 블록(Basic block)들 간의 비교를 수행한다. 차이점 파악을 위해 여러 알고리즘이 사용되고 있지만 이해하기 쉬운 알고리즘 3개만 간략히 설명하면 다음과 같다.


1. Hash Matching

말 그대로 함수의 raw 바이트를 비교함으로써 두 함수의 매칭을 시도하는 것이다. raw 바이트가 같다면, 당연히 내용도 같다고 볼 수 있기 때문에 두 함수의 유사도 평가값은 높게  나타난다.


2. Prime Signature Matching

컴파일러에 따른 명령 재배치(Instruction re-ordering)의 차이로 인한 매칭 실패를 방지하지 위해 사용되는 알고리즘이다. Small Prime Product라고 불리며 명령(Instruction)마다 고유의 값을 부여하고, 해당 명령(Instruction)이 나올 때마다 값을 더하는 방식이기 때문에 명령(Instruction)이 재배치되더라도 매칭 시킬 수 있다.


3. String References

함수나 기본 블록(Basic Block) 안에서 사용되는 문자열을 비교하여 같다면 긍정적인 점수를 주는 방법이다.


추가적으로, BinDiff는 이외에도 약 10여개 이상의 휴리스틱 알고리즘을 사용하고 있다. 가령 기본 블록(Basic Block)의 개수나 호출하고 있는 함수의 개수 또한 매칭 평가에 영향을 주는데, 자세한 내용은 BinDiff의 매뉴얼에 기술되어 있다. 각 알고리즘마다 평가점수가 다른데, 예를 들어 Hash Matching일 경우 바이트 값을 이용해 비교를 하기 때문에 평가 점수가 매우 높다.


func_1과 func_3의 가장 큰 차이점 두 가지를 꼽자면, func_3에는 func_1에는 없는 분기문이 존재하고, 함수가 더 많이 호출됐다는 점이다. 그래서 이 함수의 유사도 평가가 낮게 평가된 것이라고 할 수 있다.


정리를 해보면, 일반적인 Binary Diffing은 취약점을 찾는데 사용하기가 쉽지 않다. 취약점을 발견하기 위한 Binary Diffing 휴리스틱 알고리즘을 새로 개발해야 우리가 원하는 목적을 달성할 수 있다는 얘기다.


이를 해결하기 위해 몇 가지 알고리즘을 작성하고 시도해봤다. 가령 의미론적으로 서로 같은 코드를 만들기 위한 알고리즘을 작성했다. 예를 들어, func_3 코드에서는 분기문이(if-else) 버그 헌팅 시에는 의미가 없기 때문에 무시했고, printf 함수 역시 프로그램 실행에 의미를 갖지 않기 때문에 printf도 무시했다. 이럴 경우 유사도 평가값이 획기적으로 올라가게 된다.


이외에도 여러 알고리즘을 고안해 적용한 결과, 실제 Kamuse 벤더의 프로그램에서 제로데이 취약점을 찾아낼 수 있었다(해당 취약점에 대해서는 Kamuse 벤더에게 이미 리포트 했다). Kamuse 프로그램은 엔씨소프트의 온라인 게임들에 사용되는 모듈로써, 사용자가 게임 클라이언트를 다운로드할 때 실행된다.


해당 모듈은 P2P 기능을 수행하기 위해 네트워크 포트를 열기 때문에, 만약 Kamuse 모듈이 실행되고 있는 PC라면 해커에게 원격에서 해킹 당할 수 있는 취약점이라고 할 수 있다. 이번 취약점은 사용자 데이터를 부적절하게 처리해 발생하는 버퍼 오버플로우 버그로 지난 ISEC 2012 발표에서 최초로 소개된 바 있다.


이번 연구는 아이디어의 실현 가능성을 봤다는 점에서 의미를 찾을 수 있지만, 아직 해결하지 못한 문제점들이 많이 존재한다. 대표적으로 컴파일러의 종류에 따른 코드 제너레이션, 최적화 강도에 따른 코드 변화 등을 위해 해결해야 할 부분이 많다. 또한, 버그 헌터의 입장에서 봤을 때 의미론적으로 같은 함수 집합(예를 들어 strcpy, memcpy, sprintf 등)을 그룹화해 처리하는 등의 작업이 필요하다.


무엇보다 이 연구가 지금보다 많은 발전을 거두기 위해서는 로우레벨에서 진행되어야 하는 리버싱 오토메이션 작업이 필요하다. 이를 이루어내기 위해서는 대단히 많은 작업시간이 필요할 것으로 보인다. 아직 많은 연구가 필요한 분야이지만, 이와 관련해서는 필자가 속해 있는 고려대학교 IAS lab에서 지속적인 연구를 진행할 계획이다.


참고로 이번 기고는 지난 9월 ISEC 2012에서의 발표내용을 정리한 것으로, ISEC 발표 이후 해외의 해커들에게 좋은 피드백들을 많이 받았다. 그 가운데 한 소식에 의하면, 해외의 몇몇 보안전문가들에 의해서 10년 전쯤 Binary 레벨에서 패턴을 검사하여 취약점이 존재하는지 알아보는 시도를 했던 것으로 전해졌다. 그러나 당시는 지금처럼 리버스 엔지니어링 (Reverse Engineering) 프로그램이 다양하지 않았고, 기술수준도 지금보다는 낮았기 때문에 별다른 성과를 거두지 못한 것으로 알려졌다.

[글_이 승 진 고려대학교 정보보호대학원 IAS 랩 석·박사과정/BoB 멘토/GrayHash 수석(beist@grayhash.com)]  


Posted by wychoi
,