주요 내용으로 건너뛰기

[논문리뷰] 리눅스 악성코드 연구 동향

Review : Understanding Linux Malware

정보보안 학계에서 Top Conference 중 하나로 불리는 IEEE Symposium on Security and Privacy 2018에서 [Understanding Linux Malware] 라는 논문이 발표되었다. 사실 제목만 읽었을 때는 좀 의아했다. 악성코드에 대한 연구 자체는 이미 지천에 널려있는데, 어떤 Novel 한 접근도 아닌 단순 Understanding을 제목으로 한 논문이 어찌 IEEE S&P에 accept 될 수 있단 말인가? 

하지만 내용을 좀 더 자세히 들여다보니, 그럴만한 이유가 어느 정도는 있었을 듯 수긍이 된다. 악성코드에 대한 대부분이 연구는 당연히 Windows를 Target으로 삼았다고 단정하고 시작해왔던 반면, Linux에서의 악성코드는 사람들에게 그다지 큰 관심사가 되지 않았던 것이다. 연구자들은 말을 베베 꼬아서 써놨지만 그냥 솔직히 까서 말하자면 '돈이 되지 않아서' 지금까지 연구가 진행되지 않았다고 나는 생각한다. 그런 의미에서 이 논문은 리눅스 악성코드에 대한 최초의 포괄적인 연구의 시초가 되었다고 자칭한 것에 대해 용인할만한 부분이 있는 것 같다. 저자들은 프랑스의 EURECOM 대학 연구원들인데, 2 저자가 Cisco Systems 소속 직원인 것을 보면 이것은 아주 높은 확률로 회사에서 대학에 산학협력으로 투자를 하여 연구비 지원을 한 프로젝트라고 보인다. 시스코로 말할 것 같으면 다수의 IoT 장비를 마케팅하는 곳이니 당연히 리눅스를 기반으로 작동하는 악성코드의 위협에 대해 민감한 것은 당연할 것. 몇 년 전 있었던 Mirai botnet 같은 사건에서 Cisco가 해당 분야에 관심과 투자를 아끼지 않은 것은 매우 바람직하다고 본다.

뇌피셜은 이쯤해두고, 아래 논문을 구체적으로 읽어보자.

[Cozzi, Emanuele, et al. "Understanding Linux Malware." IEEE Symposium on Security & Privacy. 2018.] 


0. Abstract

지난 20여 년간 보안 전문가들은 윈도우즈 운영체제 기반의 환경에서 활개를 치는 다양한 악성코드에 대항하여 고군분투해왔다. 그러나 최근 다양한 임베디드 장치와 IoT 기기들이 범람함에 따라, 악성코드 역시 그 활동범위를 확장하고 있다. 이러한 임베디드 장비는 그간 통상적으로 이용되던 개인 컴퓨터와는 사뭇 다른 형태를 띤다. 대부분의 PC들이 x86 형식의 아키텍처 상에서 작동해왔지만, IoT 기기들이 작동할 수 있는 시스템 구조는 훨씬 더 다양하다. 이는 자연스럽게 다양한 아키텍처상에서 작동할 수 있는 리눅스 운영체제의 활용을 높였고, 결국 악성코드 제작자의 활동범위마저도 리눅스 무대로 넓히는 결과를 초래하였다.

하지만 이러한 리눅스 악성코드에 대한 이해를 돕기 위하여 이들을 체계적으로 분석하고 구조화한 학술적 접근이 아직까지는 부족한 실정이다. 현재까지 그나마 조금 알려져 있는 리눅스 악성코드인 Marai 봇넷 등 특정한 종류 혹은 family(科)에 국한해서 일부 네트워크 관련 흔적들을 살펴보는 것에 그치고 있고, 그마저도 개개인의 블로그 포스트 등 자료들이 파편화되어 있어 한 곳에 일목요연하게 정리되어있지 않다.

본 논문이 바로 이러한 학문적 갈급함을 채워줄 시금석이 될 것이라 생각한다. 본 논문에서는 먼저 리눅스 시스템에서의 악성코드를 분석할 때 직면하게 되는 몇가지 난제들을 살펴보도록 한다. 그리고 이를 극복하기 위해 리눅스 악성 코드들을 동시다발적으로 처리할 수 있도록 특화된 분석 시스템을 최초로 제안할 것이다. 해당 체계를 통해 1년 이상 축적된 10,548 개의 대규모 악성코드 샘플을 분석하고 그 통계 및 결과 보고서를 확보할 수 있도록 할 것이다. 이러한 연구를 통해 리눅스 시스템에서의 악성코드 분석 분야에 귀중한 토대를 제공할 수 있으리라 기대해본다.


1. 개요

통계에 따르면 개인용 PC 시장의 점유율 83%가 Microsoft의 Windows 운영체제이기 때문에 악성코드 제작자들 역시 윈도를 타깃으로 삼아왔으며, 이에 대항하여 보안 전문가들이 악전고투를 벌여온 것도 어언 20년이 넘었다. 하지만 사물 인터넷(IoT)을 중심으로 산업이 재편되면서 각종 임베디드 장치가 범람하고 있는 추세이다. 이러한 장치들은 기존의 PC와는 다른 환경에서 작동하는데 예를 들면 x86 Architecture가 아닌 다른 종류의 CPU를 주로 사용한다는 것이다. 또한 운영체제 역시 Unix 기반을 채택하다 보니 아무래도 오픈소스인 리눅스가 폭넓게 적용되고 있다. 악성코드 제작자들이 이러한 상황에 편승하는 것은 그리 놀라운 일이 아니다. 그들은 이미 리눅스 기반에서 작동하는 악성 프로그램을 제작하기 시작했다. 때문에 이러한 동향에 대한 학문적 접근이 필요한 상황이다. 


2. 난제

  • 목표 시스템의 다양성 : 리눅스는 컴퓨터 아키텍처의 종류(CPU: Intel, ARM, MIPS, Motorola, Sparc)와 리눅스 배포판(OS: Linux, BSD, Android), 사용하는 라이브러리(Libraries: glibc, uclibc, libpcap, libopencl)에 따라 상이하게 동작한다. 특히 최근 IoT 기기들이 늘어남에 따라 컴퓨터뿐만 아니라 공유기, 프린터, 카메라, 스마트 TV, 의료기기 등에서 동작하는 Linux System이 즐비하다. 이러한 각기 다른 환경에서 동작하는 악성코드를 분석하기 위해서는 알맞은 환경에서 분석이 이루어져야 하는데, 윈도우나 macOS에 비해 월등히 많은 플랫폼이 존재하기에 분석 환경을 구축하는 것 자체가 어렵다.
  • 정적 링크 방식 컴파일 : 위에서 설명한 것과 같이 워낙 다양한 환경의 리눅스 시스템이 산재하다 보니, 환경에 구애받지 않고 실행될 수 있는 바이너리를 만들고 싶다는 생각이 자연스레 들 것이다. 이러한 방식을 정적 링크(Static Linking)이라고 하는데 이렇게 컴파일하게 되면 바이너리가 호출하는 API Call 등이 모두 하나의 실행 바이너리에 통째로 들어가게 된다. 그렇게 되면 악성코드 제작자는 별도로 의존성(dependency) 문제에 골머리를 썩힐 일이 훨씬 줄어든다. 한편 악성코드를 리버싱하는 사람의 입장에선 어떨까? libc 등을 통해 어떤 API 함수가 호출되는지만 알아도 분석이 훨씬 쉬울 텐데 정적 링크로 인해 찾기가 어려워지게 되고 만약 Kernel ABI와 호환이 되지 않는 경우에 대하여 crash가 발생하면 좀처럼 갈피를 잡기가 어려워질 것이다. 
  • 분석 환경 : 분석 환경을 어떻게 마련하느냐에 따른 기준을 확립하기가 어렵다. 앞서 언급했듯이 리눅스가 지원되는 최대한 다양한 환경(CPU, OS, 라이브러리)을 지원하는 것이 최선이겠으나 한계가 있으므로 중요도나 활용도 측면에서 우선순위가 높은 것을 추려서 구축해야 했다. 또한, 리눅스의 독특한 개념인 '권한(Privileges)'을 어떻게 설정하느냐가 관건이다. 대부분의 악성코드 분석 시스템은 바이너리를 실행할 때 관리자 권한(root or Administrator)을 부여하지는 않는다. 일부 악성코드가 권한 상승을 꾀할 가능성이 있지만 그렇지 못한 경우 피해를 입힐 수 있는 정도도 상당히 제한된다. 하지만 임베디드 리눅스 등의 환경에서는 root 권한만으로 작동하는 경우도 있기 때문에, 연구의 목적을 어디까지로 정할지가 고려 대상이다. 이 부분에 대해서는 3단원에서 더 구체적으로 다룬다.
  • 기존 연구의 부재 : 논문 저자들이 자신들의 연구가 최초임을 강조하는 이유는 관련한 기존 연구의 부재로 인해 정보가 턱없이 부족하였다는 것이다. 예를 들어 리눅스 기반의 악성코드를 분석할 수 있는 환경을 만들 때 파이프라이닝 시스템을 어떻게 설계할 것이며 어떻게 그들을 평가할 것인지에 대해 명확한 기준이 마련되어 있지 않다는 것이다. 또한 실험에 사용할 데이터셋을 마련하기가 쉽지 않았다고 하는데 그렇다보니 기존 연구 중 허니팟(honeypot)을 통해 봇넷(botnet) 악성코드를 수집한 것을 주로 활용했다고 한다. (그래서 해당 연구와의 bias가 일부 생겼을 가능성이 있다.)


3. 악성코드 분석 시스템 구축

앞서 언급한 대로 리눅스 악성코드 분석에 관한 기존 동향이 부족하였으므로, 본 연구진들은 샘플들을 어떻게 수집하고 어떻게 분석할 것인지에 대한 새로운 모델을 제시하였다. 대략적인 흐름도는 아래 그림과 같다.

  • 악성코드 샘플 수집 : Intelligence API를 사용하여 2016년 11월부터 2017년 11월까지 1년 동안  VirusTotal에 보고된 항목을 기준으로 매일 200개의 샘플을 다운로드하여 수집하였다. 선별한 기준은 5곳 이상의 분석기관에서 악성으로 판정한 140개의 샘플과, AV Score 기준 1 ~ 5의 것을 골고루 섞은 60개의 샘플이다. 여기에는 리눅스 바이너리가 아닌 것은 최대한 배제하였으며, 각 카테고리 분류에 최소한 하나씩은 포함될 수 있도록 하였다. 이렇게 해서 최종적으로 수집한 샘플 데이터셋은 총 10,548개이다.(아마도 중복되는 것도 있고, 실제로 작동하지 않는 샘플도 있다 보니 Unique 개수가 줄어든 듯)
  • File & Metadata 분석 : 리눅스의 바이너리 구조인 ELF 포맷에 관련하여 이미 현존하는 다양한 분석도구가 있지만 본 연구진들은 자체 개발한 Parser를 사용하였다. 왜냐하면 기존 도구들은 악성으로 변조된 부분이나 예상치 못한 값 또는 비어있는 경우를 제대로 처리하지 못하기 때문이었다. 이러한 문제를 해결하기 위해서는 먼저 본 연구에서 활용할만한 정보를 선별해서 추출해야 했으며, 파일이 실행되는 동안에 한해서 참조되는 등의 예외 경우들을 고려해야 했다. 마지막으로는 VirusTotal 등의 백신업체에서 제공하는 결과 보고서의 AVClass 정보를 참고로 하여 현재 업계에서 대체적으로 통용되는 카테고리 Label을 준용하였다. 이를 통해 악성코드가 어떤 과(科, Family)에 속하는지를 판별하기 위한 자료로 사용하였다.
  • 정적 분석(Static Analysis) : 정적 분석 단계에서는 '바이너리 코드 분석'과 '패킹 탐지'로 절차를 구분하여 수행한다. 바이너리 코드를 분석하는 것은 IDA Pro의 스크립트를 자체적으로 작성하여 몇 가지 주요 항목들을 검사한다. 만약 바이너리가 패킹되어 있는 경우 앞 단계에서 바이너리 코드 분석이 정확히 이루어지지 않았을 것이다. 이런 경우 정적으로 언패킹이 가능한지 확인해서(UPX 등) 언팩 하고 그 결과물을 다시 피드백하여 정적 분석을 수행하였다. 언팩이 불가능한 경우는 별도로 구분하여 데이터베이스에 기록함으로써 추후 동적 분석 단계에서 차별화된 절차를 밟을 수 있도록 하였다.  
  • 동적 분석(Dynamic Analysis) : 동적 분석을 수행하기 위해 에뮬레이터를 준비하였다. 에뮬레이터는 인텔 기반의 32bit와 64bit 머신을 KVM 가상화 솔루션을 이용하여 준비하였고, QEMU를 사용하여 ARM, MIPS, PowerPC (32bit)를 마련하여 총 5개의 환경을 구축했다. 이를 통상 샌드박스(sandbox)라고 부르는데, 샌드박스 리눅스 내부에서 동작하는 일부 도구들(SystemTap)이 각기 호환될 수 있도록 수정 작업을 병행하였다. 샘플들은 샌드박스 환경에서 5분 동안 실행되며, 패킹 탐지를 통해 언패킹이 가능한지 확인하는 동작을 먼저 수행한다. 그리고 실제 프로그램을 동작하여 수행되는 system call 또는 userspace function 등을 추적한다. 추출된 결과는 텍스트 파일로 떨어지는데 그것을 다시 파싱 하여 유의미한 정보를 추출해낸다. 만약 프로그램이 권한 부재로 인해 정상적으로 작동하지 않을 경우, 루트 권한을 일시적으로 부여하여 재 테스트하고, 각각의 결과를 비교한 데이터를 추출한다. 이를 통해 권한에 다른 영향도를 분석할 수 있도록 하였다. 마지막으로 네트워크 기능이 포함되어있을 가능성을 고려하여 부분적으로 네트워크 연결을 허용한 후 그 결과를 모니터링한 PCAP 파일을 수집하여 트래픽 분석을 실시한다.


4. 실험 데이터

연구진의 최종 Dataset은 10,548개의 ELF 실행 바이너리이다. 표1에서 각각의 Architecture에 따른 샘플 개수와 전체 중 차지하는 비율을 요약하였다. 모두 리눅스 환경에서 동작하는 악성코드이며 작게는 134 bytes의 단순 백도어 기능을 가진 것부터, 크게는 14.8 MB의 Go 언어로 작성된 Botnet 관련 코드였다.

이 악성코드들의 기능을 살펴보기 위해 IDA Pro 및 자체 개발한 방법으로 ELF 헤더 및 참조 라이브러리들을 분석하였더니, 어떤 함수를 주로 사용하는지(malloc 은 쓰지만 free는 하지 않는다거나, socket을 많이 사용한다거나.. 엔트로피는 어떻게 되는지 등)를 파악하였고 자세한 내용은 부록을 통해 제시하였다.

  • Malware Families : 이렇게 구한 샘플을 분류하기 위해 AVClass 도구를 사용하였고 (참고 : Sebastián, Marcos, et al. "Avclass: A tool for massive malware labeling." International Symposium on Research in Attacks, Intrusions, and Defenses. Springer, Cham, 2016.) 전체 데이터 샘플 집합 중 83%(총 108개)에 연관성을 확인할 수 있었으며 이것을 Family 단위로 묶었다. 가장 높은 비중을 차지한 것은 예상대로 대규모 DDoS에서 주로 악용되는 Botnet 관련 악성코드였으며 그 이유는 보안이 취약한 IoT 장비를 감염시켜 원격으로 제어하는 경우가 많기 때문인 것으로 추측되며 Shodan이나 ZMap 등을 통해 손쉽게 접근 가능하기 때문일 것이다. 이런 개별 장비 하나하나의 위력은 미미하지만 티끌모아 태산이라는 말처럼 전 세계적으로 연결되어있는 수많은 장비들이 연합된다면 아무리 Low power 여도 무시 못할 위력을 갖게 된다. 뿐만 아니라 일부 악성 소스코드들이 Github 등을 통해 거의 오픈소스로 접근 가능하기 때문에 누구나 손쉽게 내용을 복제하여 유사품을 만들어낼 수 있다는 점도 한몫했을 것으로 보인다. 리눅스 관련 악성코드에는 봇넷뿐만 아니라 백도어, 랜섬웨어, 암호화폐 채굴, 은행 정보 갈취, 고전적인 파일 감염 기법, APT 공격을 위한 RAT, 웹쉘 등 다양했다. 이러한 항목들을 하나하나 세세하게 다룰 필요도 있지만, 본 논문에서는 나무에 집중하기보다는 전체적인 숲을 그려보는 것에 우선적인 목적을 두기로 한다. 이후 일부 특이사항에 대해서는 6장에서 잠시 더 논의하도록 하겠다. 


5. 리눅스 악성코드 유형 분류

본 단원에서는 연구진이 취합한 리눅스 기반의 악성코드들을 분석하여, 특별히 유의미한 행위들을 자세히 살펴보고, 가능한 경우 각각의 형태에 대한 분포 및 통계를 제공하고자 한다. 본 단원의 목표는 서로 다른 종류의 악성코드 계열(ex : Botnet family와 랜섬웨어)을 구분하려는데 있는 것이 아니라, 악성코드 제작자의 관점에서 그들이 일반적으로 사용가는 기법들(패킹, 난독화, 프로세스 삽입, 지속성 유지, 탐지 회피)에 집중하려는 것이다. 지금까지 이 주제에 대한 폭넓은 토론이 아직 이루어지지 않았으며 그렇기에 본 단원의 통찰이 향후 Linux 기반 악성코드들이 가지는 대체적인 특색들을 더 잘 이해할 수 있도록 도울 것으로 기대하며, 향후 연구에서 각각의 개별 유형들을 더욱 심도 있게 진행할 수 있도록 참고자료로 활용할 수 있을 것이다.

A) ELF 헤더 조작(ELF headers Manipulation)

ELF(The Executable and Linkable Format)은 리눅스의 실행 바이너리 형식 표준이다. 이 구조는 내부적으로 복잡한 형태를 띠고 있는데, 공격자들은 이 부분을 조작함으로써 분석가들에게 교란을 준다. 특히 e_ident, e_type, e_machine 등의 항목들은 ELF 파일이 메모리에 로드되기 전 단계에서부터 커널에서 해석이 이루어지기 때문에 이 부분에 문제가 생기면 컴퓨터는 오작동을 할 수 있다. 본 연구진들이 분석한 악성코드들 중 이러한 행위를 하는 샘플들이 취하는 형태는 크게 두 가지다. 하나는 변칙적(anomalous)이지만 어쨌거나 ELF의 명세를 준수한 경우이고, 다른 종류는 ELF의 명세 자체를 준수하지 않지만(invalid) 그럼에도 불구하고 운영체제에 의해 적절히 실행되어버리는 경우이다.

이러한 정상적이지 않은(혹은 악성인) ELF 파일들은 대다수의 분석도구들에서 장애를 유발하는데, 표 3과 같이 readelf, GDB, pyelftools에서 많은 에러가 발생함을 보여주고 있다. 결국 IDA Pro 7(본 논문을 작성하는 시점에서의 최신 버전)만이 손상된 ELF 파일들을 무리 없이 분석할 수 있었기에 추후 이루어질 분석 파이프라인에서 IDA Pro를 채택하기로 하였다. 또한, ELF header를 파싱 하는 과정을 조금 더 효율적으로 처리하기 위해서 직접 새로운 ELF Parser를 설계하였다. 이를 통해 비정상적으로 세팅되어 있거나 명세가 실제와 다른 경우, 또는 잘못된 값 또는 악성 형식의 ELF Header에 대해서도 작동할 수 있도록 하였다. 

B) 지속성(Persistence)

악성코드가 대상 시스템에 침투를 성공했다면, 가장 먼저 해야 할 것이 무엇일까? 단순히 일회성으로 끝나버릴 것이 아니라면, 해당 운영체제가 종료되는 경우에서도 적절히 은둔하였다가 재부팅 시 다시금 실행되어야 한다는 것이다. 이것을 지속성(Persistence) 유지라고 하는데 Microsoft Windows와 같은 경우 이미 많은 전략들이 있으며 분석가들 역시 Registry key에 부팅 시 자동 실행되도록 되어 있는 부분이나, 유저 로그인 시 또는 특정 이벤트 발생 시에 작동하도록 스케쥴링되어 있는 항목들을 빠르게 찾아내곤 한다. 하지만 리눅스 환경에서는 레지스트리가 없기 때문에 악성코드는 이와 유사하면서도 사뭇 다른 방법을 사용할 수밖에 없다. 본 연구를 통해 관찰된 지속성 관련행위는 크게 네가지 유형으로 분류된다.

  • Subsystems Initialization : 시스템이 부팅될 때 자동으로 실행되도록 설정하는 방법으로 가장 많이 사용되는 것은 /etc/init.d 에 rc.local로 등록하는 것이다. 이는 정상적인 프로그램들도 많이 사용하는 방식이다. 다만 이 방법은 등록 작업을 수행할 때 권한이 부여된 상태이어야만 한다. 만약 root가 아닌 혹은 권한 없는 계정으로 ELF 파일을 실행한 상황이면 /etc/init.d 등의 환경설정을 마음대로 수정할 수는 없다.
  • Time-based Execution : 두 번째로 방법은 유닉스 시스템에서 시간 기반의 작업 스케쥴러인 cron을 사용하는 것이다. 악성 ELF 파일들이 적절한 권한을 얻어 행동을 개시하기 시작할 때 cron 설정 파일을 수정함으로써 특정 시간 간격으로 자기 자신을 계속 실행할 수 있도록 할 수 있다. 이 방법도 마찬가지로 crontab 유틸리티를 수행할 때는 권한 부여가 필수적인데, /var/spool/cron을 변경하기 위한 SUID 관리를 하기 때문이다.
  • File Infection and Replacement : 또 다른 방법으로는 대상 시스템에 이미 존재하는 어플리케이션을 바꿔치기하거나 감염시키는 방법으로, 기존의 바이러스(전염성) 방식의 ELF 기법이다. 즉 이미 시스템에 존재하는 다른 정상 ELF 파일(ex: /bin 하위의 ls 등의 파일들)을 변조해서, 프로그램의 시작 부분에 악성 데이터를 삽입한 후 원래의 기능이 동작될 때 자연스럽게 악성 기능도 함께 첨부되도록 하는 것이다. 이는 고전적인 기법임에도 불구하고 최근까지 여전히 활개를 치고 있는 유효한 방식이다.
  • User Files Alteration : 사용자의 홈 디렉토리에 있는 쉘 환경설정 파일들을 변경함으로써 해당 유저에 한해서만 지속성을 유지할 수 있도록 하는 방안이다. 이렇게 할 경우 대상 시스템을 사용하는 다른 유저들에게는 영향을 미칠 수 없다. 때문에 이런 방식을 채택한 악성코드의 비율이 가장 미미하다.

종합적으로, 표 4는 각 기법을 사용하는 샘플의 비율을 나타낸다. 적어도 하나의 지속성 전략을 취하는 악성코드는 전체 중 21%에 불과하다. 그중에는 목적 달성을 위해 여러 기법을 혼용하는 경우도 있는데 만약 시스템에 대한 전역 권한 획득에 실패한 경우 가장 일반적으로 선택되는 차선책은. bashrc 나 프로파일 등의 사용자 관련 파일을 변경하여 그나마 해당 사용자 권한으로라도 제한적인 지속성을 유지하는 것이다. 

C) 위장술(Deception)

악성코드도 스텔스(Stealth) 전술을 사용한다. 사용자로 하여금 착각을 불러일으키는 이름으로 속이거나, 비정상적으로 티가 나는 이름이 프로세스 실행 목록에 표기되는 것을 방지하기 위해, 언뜻 보기에 그럴싸하면서도 유익해 보일만한 이름을 사용하여 자신을 숨긴다. 이는 이미 윈도 악성코드들도 많이 사용하는 방법으로, 본 연구진이 조사한 통계에 따르면 50% 이상의 샘플들이 정상 애플리케이션으로 행세하여 작동하는 것을 확인하였으며 prctl 시스템 콜의 PR_SET_NAME을 사용하거나, 아니면 단순히 프로그램의 argument를 변경하여 지정함으로써 이러한 행위를 한다. sshd 또는 telnetd 같은 자주 사용되는 프로세스의 이름을 표절하여하는 경우도 있었고, 단순히 (제목 없음)으로 실행하거나, 작동 시마다 매번 무작위적으로 이름을 바꾸어서 실행하는 경우도 있었다. 그러나 특이하게도 보다 복합적인 방식으로 속임수를 사용하는 케이스는 없었다. 그러므로 그저 원본 프로세스와의 실행 경로 및 이름에 대한 정보만 꼼꼼하게 점검하면 손쉽게 색출할 수 있을 것이다.

D) 권한 요청

리눅스 기반의 악성코드들은 실행될 때의 유저 권한이 일반인지 아니면 최고 관리자(root, administrator)인지에 따라 큰 차이가 있다. 본 연구에서는 먼저 해당 샘플을 일반 유저 권한으로만 수행한다. 그리고 실행되는 동안에 관리자 권한을 요청하는 것이 탐지된다면, 해당 샘플에 루트 권한을 부여하여 다시 한번 테스트한다. 실제로 전체 데이터셋의 25%(2637)의 샘플들이 두 수행 결과에서 상이한 행위를 보인다는 것을 확인할 수 있었다. 

표 7은 일반 유저 권한으로는 관찰되지 않지만 루트 권한으로 실행될 때에는 동작하는 기능들의 목록을 나타낸다. 특히 권한이 상승된 상태의 쉘 명령어를 통해 보호된 영역의 디렉터리에 접근하여 임의로 파일을 생성하거나 삭제하는 일들을 주로 수행하는 것으로 확인되었다. 뿐만 아니라 일부 악성코드들은 sandbox 탐지 기능을 수행하기도 하였으며, ssh daemon을 강제로 종료하거나 전체 파일 시스템을 삭제해버리는 행위를 하기도 하였다. 권한 상승과 관련하여 보다 구체적인 악성 행위들을 두 가지 카테고리로 분류하여 자세히 설명하도록 하겠다. 하나는 권한 상승 취약점 악용(privileges escalation exploits)이고 다른 하나는 OS Kernel과의 상호작용이다. 

  • Privileges Escalation : 본 연구에서 관찰된 샘플들은 리눅스 커널의 알려진 취약점들(CVE-2017-7308, CVE-2017-6074, CVE-2017-5123, CVE-2017-1000112, CVE-2016-9793, CVE-2016-8655, CVE-2016-5195, CVE-2016-0728, CVE2015-1328, CVE-2014-4699) 등을 통해 이미 공개된 PoC 코드를 활용한다는 것을 알 수 있었다. 그중 가장 많이 사용된 취약점은 CVE-2016-5195이었다.
  • Kernel Modules : System Call Trace를 사용하여 해당 시스템에서 커널 모듈을 로드하거나 언로드 하는지를 추적할 수 있었다. 2637개의 샘플이 root 권한으로 재수행 되었을 때 그중 정상적으로 커널 모듈을 로드할 수 있었던 것은 단 15개뿐이며그들 중 아무것도 procedure를 언로드 하지 못했다. 왜냐하면 동적 분석 시 미처 고려하기 힘든 외부 환경적 요인이기 많기 때문이다. 

E) 패킹과 다형성 (Packing & Polymorphism)

패킹은 악성코드 제작자들이 채택하는 가장 복잡한 난독화 기술임과 동시에 가장 일반적으로 많이 사용되는 기법이다. 패킹이 적절히 적용된 경우라면 악성코드에 대한 정적 분석이 원천 봉쇄되고, 수작업으로 분석을 해야 하는 리버스 엔지니어에게도 굉장한 노동을 요구되어 효율을 떨어뜨린다. 마이크로소프트 윈도 시스템의 경우에는 수백 종류가 넘는 상용 또는 무료, 기타 알려지지 않은 다수의 패커들이 존재하지만 리눅스 환경에서는 사치와 같은 이야기이다. 물론 몇 가지 ELF packer들이 학술적으로 제안되기는 하였지만 단지 개념 증명에 지나지 않는 실정이다. 그나마 유일한 예외는 1998년에 도입된 오픈소스 패커인 UPX이다. 이 패커는 무료이면서 다양한 운영체제에서 호환이 되기 때문에 현재까지도 유용하게 사용되고 있다. 

이처럼 어떤 기술로 패킹되었는지를 자동으로 분석하여 판별해내는 일은 굉장히 어려운 문제이며 이미 학계 및 업계에서 많은 연구가 진행되고 있다. 본 연구진들은 이러한 연구성과들을 종합한 휴리스틱 기법을 통해 symbol의 임포트 개수, 올바르게 디스어셈블 된 코드 섹션의 비율, 식별된 함수들 등을 토대로 해당 샘플의 패커를 추정하는 기법을 사용하였다. 그 결과 UPX를 일부 개조한 형태의 유사 방법들이 지배적임을 확인하였기에 UPX의 변형들을 위주로 하여 패킹을 해제하는 과정을 분석 파이프라인에 적용하였다. 

  • UPX 변종(UPX Variations) : UPX의 기본 패키징 구조와 동일한 압축 알고리즘이 사용하였지만 기타 일부분을 조금씩만 바꾼 것을 변종(Variation)으로 구분하였으며 결국 UPX를 포함한 그 변종들이 대다수를 차지하고, 그중 가장 많은 비율을 차지한 것은 Vanilla UPX이다. 
  • 자체제작 패커(Custom packers) : 리눅스에서는 일반적으로 UPX가 기본적인 선택지이므로 기타 다른 다양한 패커를 잘 고려하지 않는다. 하지만 Mumblehard family에 속한 3개의 샘플이 자체 제작된 패커로 구현되어 있음을 확인하였다. 해당 프로그램은 Perl 인터프리터를 수행하여 명령어를 해독(언패킹) 한 후 그 내용을 다시 인터프리터로 보내는 방식을 수행하는 루틴이 포함되어 있었다. 

F) 프로세스 작동

본절에서는 리눅스 악성코드가 하위 프로세스를 생성하거나, 기존 설치된 바이너리를 수행한다거나 아니면 시스템에서 동작중이던 프로세스에 침투하는 등의 기법을 논한다.

  • Multiple Processes : 전체 샘플의 25%는 단지 자기 자신 스스로만으로 구성된 단일 프로세스로 작업을 수행한다. 9%는 새로운 프로세스를 하나 더 수행하고, 43%는 총 3개의 프로세스(프로그램을 데몬화하기 위해  double-fork를 사용한 경우이다)로 작동된다. 나머지 23%는 그 이상으로 많은 프로세스를 작동시키는 것으로 확인되었으며 가장 많은 경우 1684개까지 생성하는 것도 있었다. 이는 대부분 botnet을 통제하기 위한 C&C Server 기능에서 사용되거나, DDoS 공격을 동시다발적으로 수행할 때 활용되었다.
  • Shell Commands : 분석된 샘플 중 13%는 적어도 하나의 외부 쉘 명령을 수행하는 것으로 확인되었다. 전체적으로 93 종의 커맨드라인 명령어가 사용되었으며, 그중 가장 많이 사용되는 것이 sh, sed, cp, rm, grep, ps, insmod, chmod, cat, iptables 순이다. sed와 cp 그리고 chmod는 대상 시스템에서 지속성을 확보할 때 주로 사용되며, rm은 자기 자신을 삭제하거나 bash history 를 삭제할 때 등 흔적을 지우기 위한 목적으로 사용된다.  
  • Process Injection : 공격자가 자신의 공격 코드를 현재 동작중인 다른 프로세스에 삽입함으로써 해당 프로그램의 실행 흐름을 변경해버리는 방식을 사용하게 되면, 이와 같은 종류는 디버그 하기가 굉장히 어려워진다. 이처럼 다른 프로그램의 메모리 영역에 내용을 강제로 삽입하는 기법은 1) ptrace syscall , 2) PTRACE_ATTACH, 3) process_vm_writev system call 이 있다.

G) 정보수집

악성코드는 침투 후에 해당 시스템 내부에서 여러 가지 활동을 수행하기 위한 제반 환경을 조사하기 위해 다양한 정보를 수집한다.

  • Proc and Sysfs File Systems : proc와 sysfs에는 동작중인 시스템의 활성 프로세스 목록이나 하드웨어 설정 같은 다양한 정보들이 저장되어 있다. 악성코드들은 이러한 정보를 획득하려는 다양한 노력들을 하는데 본 연구진이 분석한 샘플들의 행위를 기준으로 분류하자면 크게 3가지로 1) 네트워크 관련 정보, 2) 시스템 환경설정 정보, 3) 프로세스 관련 정보 등을 빈번하게 추출하는 것으로 확인되었다. 표 10을 보면 /proc/net/route를 통해 라우팅 테이블 정보를 확인하는 행위가 3000 건 이상 발견되었다. 또한 /proc/net/tcp의 활성 상태의 TCP 소켓이라던지, /proc/net/dev의 송수신된 패킷 정보들을 주로 수집한다. 두 번째로는 장착된 메모리의 용량이나 CPU core의 개수 등 시스템 환경설정 관련 정보들을 탈취하곤 한다. 마지막으로 ps 등의 명령어를 통해 현재 활성 상태의 프로세스 정보를 수집하기도 하는데 이는 다수의 악성코드를 동시다발적으로 제어하기 위한 Botnet이나 암호화폐 채굴 관련 악성코드에서 주로 보이는 특징이다. 
  • Configuration Files : 시스템 환경설정이 저장되어 있는 파일들은 보통 /etc/ 경로에 위치한다. 표 12에서 볼 수 있듯이, DNS Resolver를 위한 /etc/resolv.conf 파일이나 호스트별 IP 정보가 기록되어 있는 /etc/hosts 파일, 사용자 계정 정보가 담겨있는 /etc/passwd 와 /etc/shadow 등을 주로 노린다. 참고로 가장 높은 비율을 띄고 있는 것은 앞서 B) 파트에서 설명한 바 있는 rc.local 등의 환경설정을 통해 시스템 내부에서 지속성을 유지하려는 것이다.

H) 회피 기법

회피(evasion)를 하는 목적은 악성 행위를 수행하고 있다는 사실을 가능한 한 들키지 않고 지속하기 위해서이다. 이를 위해서는 여러 가지 분석 도구들이 작동 중인지, 현재 실행되는 환경이 Real machine인지 혹은 가상 머신인지 등을 확인한다. 본 연구에서 발견된 행위들을 아래와 같이 분류할 수 있었다.

  • Sandbox Detection : 19 개의 악성코드가 현재 작동 중인 환경이 가상 머신(샌드박스) 인지 아닌지 여부를 점검하였다. 표 14는 VirtualBox, QEMU, KVM, XEN 등 다양한 가상화 소프트웨어들에 대한 동작 정보가 내포되어 있는 파일들의 경로를 나타낸다. 이들은 자신들이 실행되고 있는 환경이 샌드박스 내부라고 판단되면(물론 이때 적절한 권한이 부여되어야 함) 더 이상의 실행을 중단하고 종료되어 버린다. 하지만 만약 이들이 전체 파일 시스템을 지워버린다던가 하는 더욱 적극적인 공격을 수행하지 않는지는 의문이다. 
  • Processes Enumeration : 259 개의 악성코드가 현재 작동 중인 프로세스 목록을 조회하는 것으로 확인되었다. 이런 방식은 윈도즈 환경에서 익히 알려진 수법이다. 이들은 /proc/<PID> 디렉터리 전체를 스캐닝한다. 다만 이 목적은 꼭 Evasion에 국한된다기보다는, 해당 서버가 이미 감염된 상태이거나 특정 프로세와의 상호 작용을 점검하기 위해 가능성 여부를 확인하는 쪽으로 주로 사용되었다. 
  • Anti-Debugging : 디버깅은 통상적으로 ptrace 시스템 콜이 대상 프로세스에 attach 되어 해당 내용을 분석할 수 있도록 하는 것인데, 이때 한 프로세스에는 하나의 디버거만이 작동할 수 있다는 점에 착안하여 가장 널리 사용되는 안티 디버깅 기술은 PTRACE_TRACEME나 PTRACE_ATTACH를 스스로에게 걸어두는 것이다. 이렇게 되면 새로운 디버거가 붙으려고 할 때 탐지할 수 있게 된다. 63 개의 샘플이 해당 방법을 사용하고 있음이 확인되었으며, 한 샘플은 LD_PRELOAD 환경 변수까지 체크한다는 사실도 관찰되었다. 이는 동적으로 로드된 라이브러리에서 함수를 오버라이드 할 때 사용된다. 한편, 본 연구진들은 kernel prove 기법을 사용하여 시스템을 trace 하였기에, 해당 악성코드들의 안티 디버깅을 우회할 수 있었다. 
  • Anti-Execution : 3 개의 샘플에서는 실행을 하지 않는 방식의 회피 기법이발견되었다. DnsAmp Family에 속하는 악성코드들은 자신의 파일명이 소스에 하드 코딩된 문자열과 일치하는지 여부를 점검하는 부분이 있다. 이들은 통상적으로 샌드박스 시스템이 분석을 시작할 때 샘플 파일명을 적절히 수정하여(예를 들면 case01 등) 진행한다는 점에 착안하여 파일명이 변경된 경우 아예 실행하지 않는 방법을 사용한 것 같다. 
  • Stalling Code : 스톨링(Stalling)이란 시간을 지연시킴으로써 속임수를 사용하는 것으로 윈도의 악성코드들이 대부분 사용하는 기법이다. 샌드박스 기반의 자동화된 분석 시스템들은 일정한 시간 동안만 바이너리를 실행해보고 종료하기 때문에, 이 시간 동안만 아무것도 안 하고 버틴다면 검문을 건너뛸 수 있게 되는 것이다. 이들은 대부분 time- 또는 sleep- 관련 함수로 시간을 지연시키곤 하는데 안타깝게도 예상과는 다르게 이러한 시간 지연 함수를 자신들의 악성 행위를 속이는 용도로 사용하는 경우는 전혀 관찰되지 않았으므로 0개의 샘플로 조사되었다. 

I) 라이브러리

실행 바이너리가 라이브러리를 호출하는 방법은 두 가지가 있다. 먼저는 훨씬 더 일반적으로 많이 사용되는 실행 시에 동적 링킹(dynamically linked) 방식을 통해 외부의 라이브러리를 불러와서 사용하는 것이다. 다른 한 방법은 정적 링킹(statically linked)을 통해 실행 바이너리 안에 관련 라이브러리를 모두 통합하여 집어넣어 놓는 것이다. 이럴 경우 환경에 따른 의존성 문제가 발생할 가능성이 줄어들어 이식이 용이하다.

본 연구진이 분석한 샘플의 80% 이상이 정적 링크 방식을 사용하고 있었다. 그러나 symbol 정보를 strip 한 비율은 24%밖에 되지 않았다. 이 정보가 남아있으면 개발자가 사용한 함수나 변수명들을 알 수 있게 되어 분석이 용이해진다. 이와 유사하게 동적 링킹 방식을 수행한 샘플에서도 해당 정보를 지워버린(strip) 경우는 33%정도였다. 이 경향은 매우 흥미로운데, 악성코드 제작자가 자신의 코드가 분석가에 의해 조사되는 것을 방지하고 싶은 일련의 동기나 의지가 별로 크게 보이지 않는 것이다. 이는 Windows의 악성코드 제작자들이 굉장히 크게 복잡성을 설정하는 것과는 대조된다.

  • Common Libraries : 동적 링킹으로 로드되는 라이브러리 중 가장 많은 비율을 차지한 것은 GNU C Library(glibc)이 74.21%를 차지했다. 한편 두 번째로 많이 사용된 것은 임베디드 환경에서 동작하도록 경량화 구현된 uClibc 이 24.24%를 차지했다. 10%정도는 libgcc가 차지했는데 이는 GCC 컴파일러가 산술 연산을 할 때 사용되는 라이브러리이다. 보통 데스크톱 환경에서는 별로 사용될 일이 없지만 임베디드 장비의 경우 부동 소수점과 고정 소수점을 제대로 지원할 수 없는 경우가 있기 때문에 그런 경우를 고려하여 사용된다. 또한 흥미로운 점은, 전체적으로 200개가 넘는 라이브러리 목록이 확인되었음에도 그 편중이 매우 심하다는 것이다. 예를 들어 libncurses는 전체에서 10위를 차지했음에도 불구하고 그 비율은 전체 샘플의 1%도 채 되지 않는다.


6. 종 내부 다양성

앞선 장에서 리눅스 환경 기반의 악성코드들이 가지는 몇 가지 속성에 대해 논하였다. 일반적으로 악성코드를 분류할 때 그들의 종류를  family(科)로 구분하는데, 본 연구에서는 그 방법이 과연 유효한 것인지 의문을 제기한다. 왜냐하면 동일한  family에 속한 악성코드임에도 판이하게 다른 행위를 수행하는 경우가 아주 많았기 때문이다. 이는 리눅스에서 동작하는 다양한 악성 프로그램의 원본 소스코드들을 인터넷을 통해 누구나 손쉽게 구할 수 있기 때문에 발생한 일인 것으로 보인다.

예를 들어 유명한 Tsunami 악성코드 family를 기준으로 설명하자면, 본 연구진의 실험에서 743개의 샘플이 Tsunami로 확인되었는데 각각은 x86-64(가장 흔한)부터 (가장 드문) Hitachi SuperH 환경에서 동작하는 9가지 형태로 존재한다. 엔트로피를 분석해보면 1.85부터  7.99에 이르기까지 다양한데, 그중에서도 하나는 vanilla UPX를 사용했지만 나머지 대부분은 모두 개조된 알고리즘을 사용하고 있었다.


7. 관련 연구

윈도우의 PE 바이너리를 분석하는 기법부터 다양한 악성코드 분석 관련 연구가 많이 있었다. 반면 윈도우가 아닌 시스템에서의 악성코드 분석에 대한 연구는 비교적 적다. 리눅스의 ELF 바이너리에 대한 연구는 대략 20년 정도 흘렀고, 그러던 중 Mirai botnet과 Shellshock 사태 이후로 비교적 관심이 증가하기 시작한 것 같다.

리눅스 바이너리에 대한 동적 분석을 수행할 수 있는 환경으로 strace와 sysdig를 혼합한 Limon Sandbox 라는 프로젝트가 있으나, 안타깝게도 x86-flavoered binaries만 동작한다. Detux 라는 프로젝트는 x86, x86-64, ARM, MIPS 등을 지원한다고 하였으나 단순히 readelf 와 몇 가지 네트워크 덤프 관련 정보만을 보여줄 뿐이어서 다소 아쉬운 결과였다. 한편 Cuckoo sandbox는 윈도우 기반으로 이미 널리 운영되고 있는 프로젝트인데, 리눅스 샘플에 대한 지원도 한다지만 실상은 GUI를 비롯한 껍데기들을 연동해주는 것이지 실제로 내부적인 샌드박스 이미지 등을 마련하는 것은 오롯이 사용자의 몫이므로 원활히 이용하기에는 부담이 있다. 가장 최근 2017년 VirusTotal이 출시한 Tencent HABO sandbox 솔루션은 리눅스 기반의 악성코드 바이너리를 분석할 수 있다고 알려져 있으나, 이와 관련된 공식적인 근거자료가 공개되지 않았으며 현재는 x86 바이너리만을 지원하는 것으로 확인되었다.

IoT 악성코드와 관련한 최초의 체계적 연구로 "IoTPOT: Analysing the Rise of IoT Compromises" 논문이 있다. 그들은 telnet 기반의 허니팟을 구축하여 악성코드를 수집하였다. 그리고 Qemu와 OpenWRT를 이용한 일명 IoTBOX라는 환경을 구축하여 IoT 악성코드를 샌드박스에서 분석하였다.

Mirai Botnet 에 대한 분석 연구로 "Understanding the Mirai Botnet" 논문이 발표되었다.

그렇지만 이들의 연구는 일부 시각에 국한되어 다소 부족한 측면이 있었기에 본 연구진은 리눅스 악성코드에 대한 보다 명확한 overview를 성사시키고자 본 논문을 작성하였다.


8. 결론 

본 논문에서는 리눅스 환경에서의 악성코드에 대한 최초의 포괄적인 학술연구를 실시하였다. 특히 리눅스 악성코드에 특화된 파이프라인 방식의 분석 시스템에 대한 설계와 구현을 제시하였으며, 대규모의 악성코드 샘플들에 대하여 그들이 악성 행위를 어떤 방식으로 구현했는지를 살펴보았다. 

현재 활개를 치고 있는 리눅스 악성코드들의 복잡성이 그다지 높은 수준은 아닌 것 같지만, 그들 중 일부는 윈도우 진영으로부터 새롭게 개발된 참신한 기법들을 일부 차용하여 리눅스 환경에서도 적용할 수 있도록 시도하는 것으로 보인다. 

때문에 리눅스 악성코드 분야에 대한 연구의 중요성은 점차 중요해지고 있다. 추후 이루어질 다양한 연구들의 초석으로써 본 논문이 사용되기를 기대한다. 

Software Security Engineer

CPUU 님의 창작활동을 응원하고 싶으세요?

댓글 1

SNS 계정으로 간편하게 로그인하고 댓글을 남겨주세요.