2016년 11월 22일 화요일

실전! 스마트 폰의 삭제된 문자메시지를 복구하라


[Tech Report] 실전! 스마트 폰의 삭제된 문자메시지를 복구하라



  • AhnLab


  • 2014-06-27

SQLite의 레코드 구조와 삭제된 데이터 복구 방법
 

앞서 3회에 걸친 월간 안 연재를 통해 디지털 포렌식 관점에서의 SQLite 데이터베이스의 의미와 함께 데이터베이스 파일 내부의 삭제된 레코드에 대한 복원 방법에 대해 알아보았다. 이번 호는 지금까지 알아본 내용을 토대로 실제 데이터 복구가 이루어지는 과정을 살펴볼 차례다.

대표적인 스마트폰 운영체제인 안드로이드에서는 문자메시지(SMS) 관리를 위해 SQLite를 사용하고 있다. 이에 이번 호에서는 삭제된 스마트폰 문자메시지를 복원하는 과정을 구체적으로 살펴본다. 이 글을 통해 지난 3회의 연재에서 살펴본 내용을 복습하고, 실제 데이터에 복원 기법을 적용하는 과정을 통해 SQLite 데이터베이스에 대한 이해를 높일 수 있게 되기를 기대한다. 

 

<연재 목차>

1부_스마트폰 포렌식과 SQLite, 포렌식 관점에서의 의미(2014년 3월 호)

2부_SQLite 데이터베이스의 구조적 특징과 데이터 복구 가능성(2014년 4월 호)

3부_SQLite의 레코드 구조와 삭제된 데이터 복구 방법(5월 호)

4부_SQLite 포렌식을 통한 스마트폰 문자메시지 복구하기(이번 호)

 

 

이 글에서는 실제 데이터 복구의 과정을 확인하기 위해 현재 시중에서 판매되고 있는 L사의 안드로이드 기반의 스마트폰을 이용한다. 해당 스마트폰의 운영체제 버전은 Android 4.4.2로, 문자메시지 데이터를 추출하여 삭제된 데이터를 복원하는 방법을 살펴보자.

 

 

스마트 폰에서 문자메시지 데이터베이스의 추출




디지털 포렌식을 위해 스마트폰 데이터를 추출하는 방법은 상황과 데이터 취득 권한에 따라 다양하다. 그러나 디지털 포렌식 관건은 데이터의 무결성, 즉 어떠한 경우라도 데이터의 손상을 최소화하는 방법을 이용하는 것이 바람직하다. 따라서 스마트폰에서 기본적으로 제공하는 백업 기능을 이용하여 데이터를 추출하여 분석하는 것이 가장 안전한 방법이 될 수 있다.




이 글에서 살펴볼 안드로이드 기반 스마트폰의 문자메시지는 백업 기능으로 데이터 추출이 가능하므로, 이를 이용하여 문자 메시지 데이터베이스를 추출하여 복구 과정을 수행해본다.

 

 




[그림 1] 삭제 문자메시지와 안드로이드 백업 기능을 이용한 파일 추출 예시

 

 

스마트폰의 데이터 추출 방법은 스마트폰 단말기 및 운영체제 별로 수많은 연구자료가 인터넷 상에 공개되어 있기 때문에 이 글에서는 자세히 다루지 않았다. 인터넷에 공유되고 있는 자료를 통해 데이터베이스 파일 추출 방법을 실습해보고자 하는 독자라면 어렵지 않게 연습이 가능할 것이다.

 

스마트폰의 백업 기능을 이용하여 데이터를 추출할 경우에는 해당 스마트폰에서 백업을 지원하는 데이터만 접근할 수 있다. 백업 대상에 포함되지 않은 데이터를 분석해야 하는 경우에는 부득이 '루팅' 혹은 '탈옥'이라 불리는 관리자 권한 강제 획득 방법을 이용해야 한다. 단, 이 같은 방법은 스마트폰의 펌웨어를 일부 조작하는 과정을 거치기 때문에 포렌식 수행 과정에서 조사 대상 데이터에 대한 변조가 없다는 것을 증명해야 하는 등 다소 부담스러운 절차가 수반된다.

 

 

데이터베이스 파일의 구조



추출한 데이터베이스 파일은 SQLite 데이터베이스 전용 브라우저를 통해 내용을 열람할 수 있다. 필자는 SQLite Expert Personal 프로그램(Freeware)을 이용했다. [그림 2]는 분석 대상 스마트폰에서 문자메시지를 담고 있는 SQLite 데이터베이스 파일인 mmssms.db에 존재하는 테이블 목록을 나타낸 것이다. 파일 내부에는 수많은 테이블이 존재하는데, 문자메시지는 모두sms 테이블에 존재한다. 스마트폰의 기능이 지속적으로 확대됨에 따라 스팸 메시지 필터링을 위한 spamstring, spamnumber 테이블이나 음성 메시지 관리를 위한 voicemail 테이블 등 다양한 테이블이 추가되어 데이터베이스 파일을 이루고 있다.

 




[그림 2] mmssms.db의 테이블 목록

 

이 글을 통해 수행하는 실습의 목적은 삭제된 문자메시지 데이터를 복원하는 것이 목적이므로 sms 테이블을 대상으로 삭제된 레코드를 찾아 복원하는 방법을 살펴보자.

 

[표 1]은 sms테이블을 구성하고 있는 필드의 목록이다. mmssms.db 파일의 테이블 개수만큼이나 sms 테이블의 필드 개수가 상당히 많다는 것을 알 수 있다. 지난 연재에서 언급한 바와 같이 필드의 개수가 많고 데이터 타입이 다양할수록 삭제된 영역에서 정확한 레코드의 위치를 파악하여 추출할 수 있다. 따라서 이처럼 복원 대상 레코드가 존재하는 테이블의 필드가 다수 존재한다는 것은 디지털 포렌식 분석가 입장에서는 정말 반가운 일이 아닐 수 없다.

 

 

[표 1] sms 테이블의 구조(필드 정보)

 

테이블의 구조를 나타내는 스키마 정보를 데이터베이스 파일에서 확인하면 [그림 3]과 같다. 스키마 테이블은 파일 헤더에 생성 당시의 쿼리 문자열 형태로 존재하며, 추후 추출한 레코드에서 각 필드의 명칭을 매치하여 분석을 진행하는데 유용하게 사용할 수 있기 때문에 가능하다면 반드시 확보해둘 필요가 있다.

 

 

[그림 3] 데이터베이스 파일 헤더에 존재하는 sms테이블의 스키마 정보

 

 

문자 메시지의 삭제 전.후 레코드 비교



이제 테스트를 위해 작성한 문자메시지가 파일 내부에서 존재하는 모습과 해당 문자메시지를 삭제한 후의 변화를 살펴보자. 본 실습에서는 문자메시지 데이터베이스 파일이 삭제되기 전ㆍ후를 비교하기 위해 각각의 시점에서 데이터베이스 파일을 추출하였다.



[그림 4]는 지난 호에서도 소개한 바 있는 삭제되기 전의 레코드 구조를 나타낸 것이다. 레코드의 길이 정보와 각각의 필드 타입 및 길이 정보에 이어 실제 필드데이터가 나열되어 있는 형식이다. 

 

 

 



[그림 4] SQLite의 레코드 구조


 

이와 같은 내용을 실제로 추출한 데이터베이스의 레코드에서 살펴보면 [그림 5]와 같다. 빨간색으로 표시된 위치가 이번 실험에서 삭제할 문자메시지를 담고 있는 레코드의 시작 위치이다. 레코드의 시작 위치에 존재하는 ‘레코드 길이 정보’와 ‘Row ID’는 가변 길이 정수 형식으로 존재하기 때문에 그 내용을 해석하여 각각 차지하고 있는 데이터 크기를 파악해야 한다. [그림 5]에서는 각각의 정보가 2바이트로 표현되어 있음을 알 수 있다.



가변길이 정수의 해석에 대해 좀 더 살펴보자. ‘레코드 길이 정보’를 나타내는 0x81은 이진수로 표현하면 100000012 이고 최상위 바이트가 1이므로 0x36을 포함하여 0x8136이 최종적으로 레코드 길이 정보를 표현하고 있다. 0x8136은 이진수로 표현하면 10000001001101102가 되므로 가변길이 정수 변환 규칙에 따라 각 바이트의 최상위 비트를 제외하고 값을 산출하면 101101102이 된다. 십진수로는 182이고, 이는 RowID에서부터 레코드의 끝부분까지의 길이를 나타내므로 0x155E7B~0x155F35가 삭제 후 복원하고자 하는 문자메시지의 레코드 영역임을 알 수 있다.

 

 

 

[그림 5] 삭제되기 전의 SQLite 레코드

 

이제 레코드 데이터가 삭제된 이후의 변화를 살펴보자. 지난 호에서 살펴본 내용을 통해 SQLite의 레코드가 삭제되는 경우 상위 4바이트의 값이 [그림 6]과 같이 갱신된다는 것을 알고 있다.

 



[그림 6] 레코드 삭제 시 덮어 쓰이는 영역

 

이를 실제 추출한 데이터에서 살펴보면 [그림 7]과 같다. [그림 5]와 비교해 보면 0x155E7B 영역에 존재하는 데이터인 0x81368E0F가 [그림 7]에서는 0x000000BA로 갱신되었음을 알 수 있다. 이 중 상위 2바이트인 0x0000은 다음 비할당 블록의 위치를 의미한다. 여기서는 0값을 가지므로 현재 위치가 페이지 내에서 가장 마지막 비할당 블록임을 의미한다. 또한 하위 2바이트인 0x00BA는 현재 비할당 블록의 길이를 나타내므로, 이를 통해 0x155E7B~ 0x155F35영역이 [그림 7]에서의 비할당 영역임을 알 수 있다. 또한 이 영역은 [그림 5]에서 삭제되기 이전의 레코드 데이터가 차지하고 있던 영역과 동일함을 알 수 있다.

 

 

 



[그림 7]  데이터 삭제([그림 5]) 이후 변화되는 영역

 

이와 같이 레코드가 삭제되어 비 할당 블록으로 전환되면 해당 페이지에서 페이지의 헤더에 최초의 비할당 블록 시작 위치를 기록하여 추후 활용할 수 있도록 한다. 이 글에서 살펴보고 있는 예제에서는 [그림 8]과 같이 삭제된 레코드가 존재하는 페이지의 헤더에 0x0E7B라는 값이 갱신되어 비할당 블록을 가리키고 있다. 실제로 비할당 블록 체인은 SQLite가 추후 빈 공간을 효율적으로 활용하기 위해 관리하는 정보지만 디지털 포렌식 관점에서는 비할당 블록의 위치를 효과적으로 수집할 수 있는 정보로 활용이 가능하다.

 




[그림 8] [그림 7]의 위치를 가리키고 있는 페이지 헤더의 비할당 블록 체인 시작 부분

 

 

삭제된 영역 찾아가기

 


지금까지 안드로이드 스마트폰에서 문자메시지를 삭제한 경우 문자메시지를 관리하는 SQLite 데이터베이스에는 어떠한 변화가 발생하는지 알아보았다. 이제 실제 데이터베이스 파일에서 삭제된 영역을 찾아 레코드의 내용을 복원하는 방법을 살펴보자.


 

데이터베이스의 내용을 탐색하기 위해 제일 먼저 수행해야 할 작업은 해당 데이터베이스가 사용하는 페이지의 크기를 확인하는 것이다. [그림 9]는 실제 추출한 데이터베이스의 헤더를 나타낸다. 0x10 위치에 페이지의 크기를 나타내는 0x1000값이 빅엔디안(BigEndian) 정수형으로 저장되어 있는 것을 알 수 있다. 추가로 0x38 위치에 존재하는 문자열 변환 방식을 확인해두면 한글로 되어 있는 데이터를 변환할 때 활용할 수 있다.

 

참고로 텍스트 변환 방식은 UTF-8을 사용할 경우 1, UTF-16 리틀 엔디안(Little Endian)을 사용하면 2, UTF-16 빅 엔디안을 사용할 경우 3 의 값을 가진다. 이 글의 예제에서는 1의 값을 갖고 있으므로 안드로이드 문자메시지는 UTF-8 방식으로 텍스트 데이터를 저장하는 것을 알 수 있다.

 




[그림 9] 데이터베이스 파일 헤더에서 페이지의 크기 및 텍스트 변환 방식을 나타내고 있는 영역

 

페이지의 크기를 확인한 후에는 두 가지 방법으로 데이터베이스 파일을 순회할 수 있다.  첫 번째 방법은 파일을 순차적으로 탐색하며 데이터가 존재할 가능성이 있는 페이지를 대상으로 삭제된 영역을 분석하는 것이다. 이 방법은 전체 페이지를 순차적으로 탐색하기 때문에 일관된 구현 방법을 사용할 수 있어 상대적으로 자동화 코드 구현이 쉽고, 미탐의 여지없이 삭제된 데이터를 복원할 수 있어 결과에 대한 신뢰성을 높일 수 있다. 그러나 여러 테이블이 상존하는 데이터베이스 파일을 대상으로 하는 경우에는 스키마가 비슷한 여타의 데이터베이스 파일의 레코드가 복원 결과에 포함될 수 있다. 또한 전체 파일을 순회해야 하는 만큼 복원 속도가 다소 느려지는 것을 감안해야 한다.




두 번째 방법은 테이블 별로 트리 구조를 해석하여 순회한 후 잔여 페이지를 따로 조사하며 삭제된 영역을 분석하는 방법이다. 이 방법의 장점은 B-트리의 최적화된 탐색 방법을 이용하기 때문에 탐색 속도가 빠르고 오탐 발생이 최소화 될 수 있다는 것이다. 그러나 각각의 트리에 대한 루트페이지를 찾기 위해 트리를 순회하고 트리 구조에 포함되지 못한 영역을 계산하여 탐색을 추가로 진행해야 하는 등 구현 과정이 복잡하여 오류가 발생할 가능성이 높다. 결과적으로도 미탐이 발생할 가능성이 높기 때문에 결과에 대한 신뢰성을 입증하기 위해서는 다양한 실험의 결과와 함께 구현상의 허점이 없다는 것을 증명할 수 있는 체계가 요구된다는 부담이 있다.

 


데이터베이스 파일을 순회하며 데이터 페이지를 탐색하여 삭제된 영역을 찾는 방식을 이 글을 통해 살펴보기에는 어려움이 있다. 이에 복원 대상이 존재하는 페이지를 찾았음을 가정하고 페이지 내에서의 삭제된 레코드 복원을 진행하겠다. 별도로 실습을 해보고자 하는 독자라면 가급적 영문으로 되어 있는 플드를 포함하는 레코드를 작성한 후 삭제하여 헥사 에디터에서 해당 영역을 검색하고 복원하는 방법을 수행해보는 것도 좋다.

 

각각 장.단점이 있기 때문에 어떤 방법을 선택하느냐는 결국 분석가의 몫이다. 또한 시중의 SQLite 복원 도구가 어떠한 방식으로 동작할 지에 대해 생각하면서 도구를 사용하는 것이 바람직하다.

 

 

삭제된 레코드의 필드 값 구분 및 의미 해석



예제에서 삭제한 문자메시지 관련 레코드가 존재하는 페이지는 0x155000에 위치했다. 이때 데이터베이스 파일이 정의하는 페이지의 크기가 0x1000이므로 페이지가 존재하는 영역은 0x155000~0x155FFF라는 것을 알 수 있다.



해당 페이지에서 비 할당 블록이 위치한 곳은 [그림 8]과 같이 페이지의 헤더 부분에서 확인할 수 있다. 예제에서는 비할당 블록을 가리키는 위치가 0x0E7B이므로 실제 파일의 0x155E7B의 위치에 삭제된 데이터가 존재한다는 것을 알 수 있다.




이제 삭제된 레코드의 필드 값을 구분하기 위해 테이블 스키마를 알아보자. [그림 10]은 데이터 헤더가 갖는 값에 따라 데이터 타입을 매칭할 수 있는 표를 나타낸 것이다. 삭제된 레코드의 복원을 위해서는 정상적으로 존재하는 레코드를 통해 테이블의 스키마를 채득하는 과정이 선행되어야 한다. 예제에서는 삭제되기 이전 상태의 레코드 데이터를 통해 스키마를 채득하는 것을 설명한다. 다만, 실제 데이터 복구 과정에서는 복구 대상 레코드와 같은 내용을 가진 정상 레코드가 존재할 수 없으므로 같은 페이지 내에서 정상적으로 존재하는 다른 레코드를 통해 스키마를 채득해야 한다는 사실을 주지하고 아래 내용을 살펴보도록 하자. 

 

 

 

[그림 10] 정상적인 레코드 데이터 헤더에 위치한 테이블 스키마 정보

 

 

[그림 11]은 정상적인 레코드를 찾아갔을 때 그 내용을 해석하는 과정을 보여준다. 레코드가 시작하는 0x155E7B의 2바이트는 레코드의 길이를 나타내는 가변길이 정수이며 이어지는 2바이트 역시 1807이라는 값을 갖는 가변길이 정수로, 해당 레코드의 RowID를 나타낸다. 

 

 

 

[그림 11] 정상적인 레코드에서 데이터 헤더의 해석 및 필드 구분

 

[그림 12]는 삭제되기 전의 레코드를 SQLite Browser로 살펴본 내용으로 해당 레코드의 RowID가 계산한 대로 1807의 값을 가짐을 알 수 있다.

 

 

 

 

[그림 12] 삭제되기 전의 레코드를 SQLite Browser로 확인한 내용  

 

[그림 11]에서 레코드의 길이 정보와 RowID에 이어 0x155E7F 위치의 2바이트는 데이터 헤더의 길이를 리틀엔디안 정수로 나타낸다. 해당 길이 정보는 자기 자신까지 포함하므로 실제 데이터 헤더만의 길이는 0x3E로, 0x155E80~0x155EBE의 영역을 차지하고 있음을 알 수 있다.




위와 같은 과정을 거쳐 확인한 데이터 헤더를 앞에서부터 순차적으로 [그림 10]을 참고하여 필드 데이터의 길이와 타입을 확인하고 테이블 스키마를 완성할 수 있다. 이때 BLOB과 TEXT를 나타내는 값은 가변길이 정수를 사용하므로 반드시 모든 필드가  1바이트로 표현되는 것은 아님을 인지하고 있어야 한다.  [그림 11]에서 표시한 부분은 전화번호를 나타내는 address 필드와 문자메시지가 저장된 body 필드 등 상위 일부 필드에 대해 데이터 헤더와 연결되는 정보이다.

 

살펴본 바와 같이 정상적인 레코드의 내용을 해석하면 해당 페이지를 포함하는 테이블의 스키마 정보를 알 수 있다. 모든 필드의 타입을 해석하여 스키마를 구했다면 그 형태는 앞서 살펴본 sms 테이블의 스키마 정보인 [표 1]의 내용과 동일할 것이다. 다만, 각 필드의 이름 정보는 페이지 내부에서 확인할 수 없으므로 분석이 완료된 후에 데이터베이스 헤더에 존재하는 테이블 스키마에서 각 필드의 이름을 연결하는 작업이 필요하다.


 

 

삭제된 레코드의 최종 복구



이제 테이블 스키마라는 '열쇠'를 획득하였으니 삭제된 레코드의 복구도 막바지에 이르렀다. [그림 13]은 복구하고자 하는 삭제된 문자메시지 레코드에서 데이터 헤더의 위치를 찾아가는 과정이다. 앞서 살펴본 바와 같이 [그림 13]의 0x155E7B 위치의 4바이트는 비할당 블록을 관리하기 위한 정보로 갱신되어 있다. 따라서 정상 레코드를 해석할 때와 같이 순차적으로 데이터 헤더의 위치를 찾을 수 없으므로, 채득한 스키마를 비할당 블록이 시작되는 위치부터 순차적으로 비교하며 데이터 헤더의 위치를 찾아야 한다. [그림 13]에 모든 필드 타입에 대한 비교가 나타나 있는 것은 아니지만 지금까지 확인했던 _id 필드에서 body까지의 필드 타입만으로도 데이터 헤더의 위치를 정확하게 찾아낼 수 있다. 



 




[그림 13] 삭제된 레코드에서 데이터 헤더의 위치 찾기

 

 

이제 삭제된 레코드에서 데이터 헤더의 위치도 찾았으므로 [그림 11]에서 살펴본 바와 마찬가지로 필드의 내용을 구분하여 정확한 데이터를 추출하여 활용하기만 하면 된다.



지금까지 총 4회에 걸쳐 SQLite 데이터베이스에서 삭제된 레코드를 찾아내고 복원하는 과정을 살펴보았다. 이번 호에서 살펴본 예제 때문에 다소 복잡하게 보일 수도 있다. SQLite가 공간 활용의 극대화를 위하여 다소 복잡하게 설계된 데이터 구조를 갖고 있는 것은 사실이다. 그러나 SQLite 구조의 분석 및 복원에 성공하고 이를 통해 구현된 결과물을 얻게 되면 스마트폰 포렌식은 물론, 다양한 디지털 포렌식 분석에서 매우 유용하게 활용할 수 있을 것이다. 더불어 파일 포맷 분석에 익숙하지 않은 독자라면 SQLite 레코드 복원 과정을 통해 숙련도를 쌓을 수 있을 것이다. @










  • AhnLab 로고



  • 클라우드 분석팀 전상준 연구원




이 정보에 대한 저작권은 AhnLab에 있으며 무단 사용 및 도용을 금합니다.

단, 개인이 비상업적인 목적으로 일부 내용을 사용하는 것은 허용하고 있으나, 이 경우 반드시 출처가 AhnLab임을 밝혀야 합니다.

기업이 이 정보를 사용할 때에는 반드시 AhnLab 의 허가를 받아야 하며, 허가 없이 정보를 이용할 경우 저작권 침해로 간주되어 법적인 제재를 받을 수 있습니다. 자세한 내용은 컨텐츠 이용약관을 참고하시기 바랍니다.

정보 이용 문의 : contents@ahnlab.com


홈페이지 jQuery 라이브러리에서 CVE-2019-11358 취약점 패치 여부 확인 방법

현재 홈페이지에서 사용 중인 jQuery 라이브러리가 CVE-2019-11358 취약점 패치를 적용했는지 확인하는 방법은 다음과 같습니다. 1. jQuery 버전 확인 홈페이지 소스 코드를 확인하여 jQuery 라이브러리 버전을 직접 확인합니다. 웹 ...