본문 바로가기

[+] Forensic

[정리] TrueCrypt Forensics

Anti-Forensics에서 가장 난감한 것은 개인적으로 생각할 때 '암호화' 기술인 것 같다. 분석해야 할 매체가 앞에 있음에도 불구하고 암호화라는 갑옷으로 둘러싸여 있어 분석을 쉽사리 하지 못하기 때문이다. 암호기술은 수학적 지식을 기반으로 고안되어 있기 때문에 현재로서도 깨지 못하는 암호들이 많이 있다. 불가항력(?)을 이겨내보기 위해 디지털 포렌식 분야에서는 많은 연구가 이루어졌었다. 이때 대상으로 연구가 많이 된 것이 'TrueCrypt'라는 암호화 소프트웨어이다.


TrueCrypt는 단일 파일부터 부트 드라이브까지 암호화 대상을 가리지 않는다. 또한, 일반 사용자들이 쉽게 사용할 수 있도록 GUI 환경을 제공해 사용 범위에 있어 한계를 거의 느끼지 못한다. 이런 이유로 많은 컴퓨터 사용자들에게 자신의 데이터를 안전하게 보호할 수 있는 환경을 제공하였는데, 디지털 포렌식 전문가들은 이와 같은 환경으로 인해 어지간히 애를 먹는 것이 현실이다.


이번 글에서는 TrueCrypt의 암호화 과정을 간단히 살펴보고, 이에 대해 지금까지 어떤 연구가 이루어졌는지 알아보려 한다. 정리 차원에서 쓰는 글이므로 따로 연구를 하거나 하지는 않았다.


참고로 TrueCrypt는 개발자가 알려져 있지 않다. 더군다나 작년 쯤에 NSA 도청 사건으로 인해 민간단체에 의해 TrueCrypt 내부에 백도어가 숨겨져 있다는 의심까지 받아 민간단체에게 감사를 받은 적이 있다. 물론 결과는 '없다'로 결론지어 졌지만, TrueCrypt 개발자는 마음고생을 좀 했을 것으로 예상이 든다. 이와 맞물려 Windows XP의 지원 종료로 인해 TrueCrypt는 개발이 중단되었지만, 최근에 오픈소스 개발자들이 TrueCrypt의 개발을 재개하여 다행히도(?) 많은 사용자들이 TrueCrypt의 암호화 서비스를 계속해서 지원 받을 수 있게 되었다.


그럼 이제 TrueCrypt의 암호화 과정과 복호화를 위한 Anti Anti Forensic 기술을 간단히 살펴보자.


1. 정상 암/복호화 과정

TrueCrypt에서는 기본적으로 다음과 같은 알고리즘과 모드를 지원한다. 아마 '정보보안개론'을 공부해 본 사람이라면 한번 쯤 들어본 알고리즘일 것 이다.


[알고리즘]

 - AES

 - Twofish

 - Serpent

 - AES-Twofish

 - AES-Twofish-Serpent


[모드]

 - XTS

 - LWR

 - CBC, Outer CBC, Inner CBC


오픈소스이기 때문에 많은 연구원들에게 분석 되어 이미 분석되어 일부는 복호화를 지원하지만, 방법이 대부분 메모리 이미지에서 패스워드를 긁어와 복호화를 하는 방법이다. 대표적으로 'Passware Kit Forensic', 'Elcomsoft Forensic Disk Decryptor' 도구가 있다.


암호화 과정의 설명은 다음 그림을 참고하며 설명하도록 하겠다.


[그림 1 - 암호화]


[그림 1]은 암호화가 진행 되어 완료 된 호스트 상태이다. 주요 암호화 과정은 다음과 같다.


1) 사용자가 Passphrase를 입력한다.

2) TrueCrypt는 암호화 대상 파일을 임의로 생성한 Master Key로 암호화 한다. 여기서 기본 암호화는 AES이다.

3) 암호화 된 파일을 컨테이너(Container)라고 부르는데, 컨테이너 파일에 붙일 헤더를 생성한다. 이때 헤더는 512byte이며, 데이터를 암호화 할 때 사용한 Master Key를 헤더에 저장한다. 

4) Master Key가 저장 된 헤더는 사용자가 입력한 Passphrase와 64byte의 Salt 값을 이용해 생성한 Header Key를 이용하여 암호화한다. 이때 사용된 Salt 값은 헤더 앞부분 64byte에 평문으로 저장한다.


위와 같이 정상적인 암호화가 진행되었고, 사용자가 복호화를 원한다면 TrueCrypt는 다음과 같은 복호화 과정을 거친다.


1) 사용자가 Passphrase를 입력한다.

2) 사용자가 입력한 Passphrase와 헤더 부분에 있는 Salt 값을 이용해 Header Key를 생성하고 Header Key를 이용 해 컨테이너 헤더를 복호화 한다.

3) 복호화 된 컨테이너 헤더에서 Master Key를 꺼내 데이터를 복호화 한다.


암호 알고리즘이 복잡할 뿐 TrueCrypt의 암호화 과정은 의외로(?) 간단하다. 이러한 과정을 역이용하여 여러 복호화 방법이 고안되었는데, 이제부터 그 몇가지를 소개해보고자 한다.


2. 연구 된 복호화 과정

복호화 과정 중에 현재 사용 중이며, 현실성이 있는 방법들을 위주로 소개하니 이 점 참고하고 글을 읽어내려 가기 바란다.


2.1 Cached Passphrase in Memory

암/복호화 과정에서 가장 중요한 것은 사용자가 입력한 'Passphrase'이다. 또한 소프트웨어가 사용자 입력 값을 저장하고 사용하기 위해서는 메모리에 이 값을 저장해야 하는데, 이 값은 한군데만 남는 것이 아니라 여러군데에 남는다. 그러므로 소프트웨어가 이 값을 모두 지우기란 불가능하다.


연구원들은 이와 같은 사실을 이용 해 메모리를 이미징하고 이미징 된 메모리 이미지 파일에서 캐싱된 Passphrase를 추출 해 컨테이너를 복호화 하는 식의 방법을 고안하였다. Passphrase의 구조는 다음과 같다.


[Passphrase Structure]

#define MIN_PASSWORD 1 // Minimum possible password length

#define MAX_PASSWORD 64 // Maximum possible password length


typedef struct

{

unsigned __int32 Length;

unsigned char Text[MAX_PASSWORD + 1];

char Pad[3]; // keep 64-bit alignment

} Password;


예제)

17 00 00 00 74 72 75 65 63 72 79 70 74 70 61 73   ....truecryptpas

73 77 6f  72 64 73 65 63 75 72 65 00 00 00 00 00   swordsecure.....


Passphrase 구조는 보듯이 특별한 Signature가 존재하지 않는다. Passphrase 구조를 규칙으로 하여 Text의 내용이 문자인 부분을 모두 추출 해 사용자에게 보여주거나 길이 hex값의 특징을 Signature로 이용해 패스워드를 추출 할 수 있다. 다음은 외국 블로거가 작성한 Passphrase 추출 스크립트 코드이다.


import struct,string

def isasciistr(s):
    return all(c in string.printable for c in s)

def findPassphrases(memdump):
    img = open(memdump, 'rb', buffering=10240)

    while True:
        region = img.read(10240)
        if len(region) == 0:
            break
        startoffset = 0
        while startoffset < len(region):
            offset = region.find('\x00\x00\x00', startoffset)
            if offset > -1:
                startoffset = offset+3
                lengthField = region[offset-1]
                #we found a set of three null bytes
                length = struct.unpack('<B', region[offset-1])[0]
                if length > 0:
                    passphrase = region[offset+3:offset+3+length]
                    lengthReal = len(passphrase)
                    #make sure we aren't hurtling off the region boundary
                    if offset+3+length+1 < 10240:
                        #make sure byte right after string is a null byte (sure, more should be null, but whatever)
                        if ord(region[offset+3+length+1]) != 0:
                            break
                    #make sure that the string is as long as the field said it would be
                    if isasciistr(passphrase) and ord(lengthField) == lengthReal:
                        #print the possibility
                        print "POSITIVE: "+passphrase + " EXPECT("+hex(ord(lengthField))+")"+" GOT("+hex(lengthReal)+")"
            else:
                break

findPassphrases()

출처 : http://delogrand.blogspot.fi/2013/04/cyber-defense-exercise-2013-extracting.html


현재 Volatility에서 내놓을 TrueCrypt 플러그인도 위와 같은 방법으로 메모리 이미지에서 Passphrase를 추출한다.


2.2 Fake Header

Fake Header는 TrueCrypt를 패치하여 복호화 과정을 교묘하게 이용하는 방법이다. 캐싱된 Passphrase가 메모리 이미지에 없고, 캐싱된 Master Key만 있을 경우 사용하는 방법이다.


1) 처음에 임의의 컨테이너를 생성한다.

 $> truecrypt --text --create --encryp-on=AES --filesystem=FAT --hash=RIPEMD-160 --password=MaJ3stY --random-source=/dev/random --size=20971520 --volume-type=normal tempFile


2) 임의의 컨테이너의 헤더를 복호화 대상 컨테이너 헤더로 덮어 씌운다.

 $> dd if=tempFile of=originalFile bs=512 count=1 conv=notrunc


3) 메모리 이미지에서 Master Key 목록을 추출한다. 목록을 추출할 때에는 Cold Boot Attack 연구 때 같이 연구된 aeskeyfind 도구를 이용한다. 해당 도구는 Aes key의 구조와 Entropy를 이용해 메모리 이미지에서 Aes key와 비슷한 데이터들을 추출 해 준다.

 $> ./aeskeyfind <메모리 이미지>


4) TrueCrypt를 패치한다.


[그림 2 - 원본 코드 : Volume/VolumeHeader.cpp]


[그림 3 - 수정된 코드 : Volume/VolumeHeader.cpp]


수정된 코드가 실행 될 때는 이미 Header Key가 생성되고 컨테이너 헤더가 복호화 된 상태이다. 이때 메모리 이미지에서 추출한 Aes Key 목록을 읽어와 메모리 버퍼에 저장한 후 해당 키 값을 헤더에 있는 Master Key와 교체한다.


5) 임의의 컨테이너를 생성할 때 입력했던 Passphrase로 복호화를 수행한다.

 $> truecrypt --text --mount-options=readonly --password=ABC123 originalFile /mnt/truecrypt 


간단히 정리하면, 임의의 컨테이너 헤더로 덮어 씌어진 복호화 대상 컨테이너 파일은 사용자가 입력한 Passphrase로 컨테이너를 헤더를 복호화 할 수 있다. 하지만 헤더 내에 저장되어 있는 Master Key가 다르므로, 컨테이너의 내용은 복호화가 안된다. 이를 위해 TrueCrypt의 복호화 단계에서 헤더 내에 저장된 Master Key를 바꿔치기 하기 위해 코드를 수정하는 것이다. 바꿔치기된 Master Key 중에 유효한 키가 존재한다면 복호화는 정상적으로 수행될 것이다.


3. 정리

위에서 소개한 방법 외에도 연구된 방법은 많지만, 현재 사용되지 않고 있다. 대표적인 예로 BIOS Key Buffer에서 Passphrase를 추출하는 방법이 있는데, 이 방법은 상위버전에서 패치되어 현재는 소프트웨어가 자체적으로 쓸모 없는 값으로 Buffer를 Clear하여 Passphrase를 추출하지 못한다. 또 이외에 방법들은 위 방법들을 응용할 뿐이고 다른 부분이라고는 메모리를 어떻게 수집하는지에 대한 것이어서 다른 방법들에 대해서는 따로 언급하지 않겠다.


한번정도는 정리를 해야겠다고 마음을 먹고 있었는데 이제서야 정리를 하게되었다. 혹시 이 글이 도움이 되는 사람이 있길 바란다. :-)


아래는 각 버전별 Passphrase와 Master Key등이 메모리에 남는지에 대해 정리한 표이다.


[표 1 - 버전별 정리]


* 3.1a 버전의 경우 Passphrase가 char *buffer 공간에 있어 여러군데 캐시가 되지 않는다. 그 대신 'truecrypt.sys' 파일에 .data Section에 Passphrase를 대신하는 문자열이 하드코딩 되어 있어, 특별한 경우가 아니면 메모리에서 캐시된 Passphrase를 추출 할 필요가 없다.


** Master Key가 없는 버전들은 'truecrypt.sys' 파일의 심볼정보를 추적하면 Master Key를 추측할 수 있다.


summary는 컨테이너에 관한 정보이다.