Mafia의 진실
Mafia의 진실 2010.02.10

낚시성 제목에 방문하신 분께는 죄송합니다. 이 글은 오래전부터 twitter 사용자들을 대상으로 퍼져나가고 있는 'Mobster world'라는 온라인 MMORPG 게임과 관련된 글입니다. 트위터 사용자 분들 중에는 위와 유..

iPhoto 슬라이드 쇼

트위터에 질문이 하나 올라와 짧게 작성해봤습니다. iPhoto에서 슬라이드 쇼 생성하는 방법입니다. 1. 먼저 사진 메뉴에서 슬라이드 쇼에 추가할 사진을 선택합니다. 사진 선택은 Command 키와 마우스를 이용합니다. 2...

노키아에서 동작하는 Mac OS X 10.3

핀란드에 사는 Toni Nikkanen이라는 분이 자신의 Nokia N900 모델에 Mac OS 10.3 Panther를 설치하고 실행시키는데 성공했다고 합니다. Toni의 블로그에 가보니 Mac OS X 말고도 Windows..

들어가면서
제스리버입니다. 제가 강의를 좀 많이 하는 편인데요, PE 포맷을 가르칠 일이 있어서 어떻게 가르칠까 고민하다가  레고 생각이 번뜩 떠오르더군요. 그래서 code만 가진 상태에서 헤더를 만들어 가져다 붙이고 섹션 테이블을 만들어 붙이고 필요한 섹션도 붙이고 하는 식으로 실험을 해보았습니다. 실제 소스 코드를 가지고 설명하는 것에 비해 노가다성은 짙은 반면, PE 포맷에만 집중할 수 있어서 프로그램이 약간 약하신 분들도 그리 어렵지 않게 PE 포맷에 대해서 이해하실 수 있을 것 같습니다. 그래서 "조립하면서 배우는 PE"라는  제목의 연재물을 통해 지식을 나눠보고자 합니다. PE 포맷을 공부하시다가 중도에 포기하신 경험이 있으신 분들, 이제 막 리버싱에 대해 공부를 시작하시는 분들에게 도움이 되었으면 하는 바램입니다.

실행 파일 포맷을 왜 배워야 하는가?
리버스 엔지니어링의 관점에서 말씀을 드리자면, 다음과 같은 4가지 이유때문에 그렇습니다.
- 매뉴얼 언패킹 시 IAT를 리빌딩 하려면 실행 파일에 대한 지식이 필요합니다.
  물론 ImpRec등 자동으로 리빌딩을 해주는 툴이 있기는 하지만, 자동화된 툴은 언제나
  문제점을 가지고 있습니다. 사람이 할 수 있는 일들의 대부분은 빠르게 처리하겠지만
  모든 것을 처리해 주는 것은 아니죠. 늘 강조하는 이야기입니다만,실력을 쌓고 싶다면
  수작업이 귀찮아 질 때 또는 최소한 원리라도 이해하고 툴을 사용해야 합니다. 처음부터
  원리도 모른채 툴만 사용하는 것은  "나 바보 만들어 주세요"랑 다를 바 없다고 생각합니다.
  ^^;

- 안티 리버싱 테크닉을 이해하고 우회하기 위해서 필요합니다.
  안티 리버싱 테크닉 중에는 TLS callback 과 같이 PE 파일 포맷과 관련된 단순하면서도
  효과적인 방법들이 더러 존재합니다. 이러한 안티 리버싱을 기법을 습득하는데 실행
  파일 포맷에 대한 전반적인 지식이 필요한 것은 아닙니다. 하지만 PE에 대해서 익숙하면
  어렵지 않게 이해할 수 있겠죠.

- 코드 패치를 하기위해서는 실행 파일에 대한 지식이 필요합니다.
  리버싱을 하다보면 가끔 기존의 실행 파일에 코드를 추가하거나 하는 경우가 있는데
  이런 경우에도 마찬가지로 PE 포맷에 대한 지식이 필요합니다.

- 링커나 컴파일러를 제대로 이해하고 사용하기 위해서는 파일 포맷에 대한 지식이
  필요합니다.
  링커나 컴파일러가 제공하는 상당수의 옵션은 실행 파일과 밀접한 연관을 가지고 있습니다.
  초보 프로그래머들은 대부분 컴파일러나 링커를 효과적으로 사용하지 못하는데,
  바로 그 이유가 실행 파일 포맷을 제대로 이해하고 있지 못하기 때문입니다.

이러 이러한 이유로 실행 파일 포맷을 공부해야 합니다. 사실 주변의 많은 분들이 실행 파일 포맷에 대해 어려워하고 겁을 많이 먹는데, 사실 내용이 많다 뿐이지 어렵거나 복잡한 것은 아닙니다. 더구나 처음 공부할 때는 PE 파일 포맷에 대해서 완벽하게 알 필요도 없구요. 아뭏든 이번 기회에 같이 공부해보도록 하죠. ^^;

어떻게 공부해야 하는가?
외국의 많은 해커들은 그 유명한 Matt Pietrek의 문서를 많이 언급합니다. PE에 대한 최초(?)의 문서이었던 데다가 워낙 유명하신 분이 쓰신 글이기 때문이죠. 물론 내용도 좋습니다. 하지만 사실 Matt Pietrek님이 쓰신 문서만으로 공부하기에는 많이 시간이 걸리는 것이 사실입니다. 한국은 실정이 좀 다릅니다. 영문으로 번역해도 공전의 히트를 칠 만한 아주 좋은 책이 있기 때문입니다. 앞으로 이 글을 써나가면서도 자주 참조 할 것 같습니다. 국내에서 출간된 기술서 중 드물게보는 명서 중 하나로 이호동님께서 저술하신 "Windows 시스템 실행파일의 구조와 원리"라는 책이 있습니다. 국내에 이런 책이 있다는 것은 축복입니다. 깊은 지식을 원하는 분들께 강력 추천합니다.

"Do It Yourself" 정신은 리버서에게 아주 중요하다고 생각합니다. 위에서 언급한 책은 시간을 많이 단축하는데 도움이 되겠지만 앉아서 책만 읽는다면 사실 완전히 자기의 지식으로 만들기에는 뭔가 부족함이 느껴질 겁니다. PE 파일 포맷을 연구하는데 가장 좋은 방법은 "실행 파일 분석기"를 직접 제작하는 것이라는 의견에 대해 저 역시 전적으로 공감합니다. 허나... 불행하게도 프로그래밍에 어려움을 느끼는 분들이 많은 것 같습니다. 프로그래밍은 언젠가는 넘어야 할 산이라 도전해 보는 것이 바람직하겠지만 PE 파일 포맷에 대해 궁금해 죽겠는데, 예제 코드를 이해하기에는 좀 어렵고 그렇다고 프로그래밍 공부를 처음부터 다시하자니 시간은 없고 하시는 분들에게는 참 답답할 일이죠. 이럴때는 그냥 부딪혀 보는게 최선입니다. 뭐 손해 볼 것 있습니까? 분석하다가 실패한다고 해서 누가 뭐라하겠습니까? 분석하는 과정을 즐기면 결과로 얻어지는 지식은 하나의 작은 선물에 지나지 않습니다. 여러분들도 그러한 쾌감을 못 잊어서 계속 공부하시는 것이 아니신지요?  예전에 라디오 하나쯤은 부서보셨을 것 같은데, 그 경험을 살려 실행 파일을 분해한다음 하나씩 조립하는 방법으로 공부해보죠. 본 연재물은 앞서 언급한 대로 그렇게 구성되어 있습니다. 다소간 프로그래밍 실력이 부족하더라도 PE 포맷을 이해하시는데는 큰 어려움이 없을 거라 생각합니다. (부족한 프로그래밍 실력은 시간을 두고 계속 만회하셔야 합니다. 이런 식의 방법에도 한계라는 것은 존재하니까요...뭐 별거 있습니까? 그냥 한번 해보는 거죠.)

준비물
일단 가장 큰 무기는 Hex 에디터입니다. PE를 공부하는데 있어 라디오 부수고 조립할 때 있어 드라이버 같은 역할을 해 줄 녀석입니다. 저는 WinHex를 사용합니다. 가급적 WinHex를 사용해 보시길 권장해 드립니다. UltraEditor같은 녀석들과 죽마고우시라면 그것도 괜찮습니다. 드라이버야 자기 손에 맞으면 그 뿐이죠.

간단한 PE파일 분석기가 필요합니다. 다들 비슷비슷해서 특별히 권장할 만한 것은 없습니다. 쓰시던 것 있으시면 그걸로 하시고 아니면 PE explorer나 LordPE, Stud PE 같은 툴 사용하시면 됩니다.

그 다음으로는 큰 노트와 필기구가 필요하겠군요. 그림 그리기 편하게 큰 노트를 구입하시는 것이 좋겠습니다. 아니면 집에 굴러다니는 쓰다 남은 노트라도...


그럼 시작합니다.
 


Posted by zesrever

사용자 삽입 이미지
금까지의 시간을 뒤로 하고 나는 세상으로 걸어 나가고 있습니다.비록 서툰 걸음일지라도 열심히 걷겠습니다. 걷다보면 조그만 돌뿌리에 발이 걸려 넘어지기도 하겠지만 다시 일어나 걸을 수 있다는 사실에 감사하며 나를 반겨주는 당신에게로 한 걸음 한 걸음 열심히 열심히 걸어가겠습니다. 지금 나를 이끌고 있는 이 손을 영원히 기억할 것입니다.





Posted by zesrever
침해 대응 & 컴퓨터 포렌직에 관련한 첫번째 글이네요 ^^; 오늘은 간단한 팁에 가까운 테크닉을 하나 소개할까합니다. 사실 외국에서는 일반화된 지식같은데 혹시나 해서 잠깐 검색을 해본 결과로는 국내에 관련된 글이 없는 듯 해서 올려봅니다.  

개 요
침해 대응 또는 컴퓨터 포렌직을 목적으로 시스템을 조사하는 경우 빼놓을 수 없는 조사 대상 중 하나가 로그 파일입니다. 이러한 로그 파일 분석은 삭제되거나 저장 매체에 잔존하는 로그 파일의 복구, 손상된 로그 파일 복원, 조작 가능성 판별,  로그에 나타난 공격 시그너춰분석, 통계 정보 추출, 로그와 로그 간 상관관계 분석, MAC time 분석 등 다양한 기법과 기술을 필요로 하는 작업입니다. 오늘 주제는 바로 이 로그 분석과 관련 있습니다. 뭐 다 다룰려면 한두 꼭지의 글로 되겠습니까? ^^;  이 글에서 다룰 내용은 제목 그대로 "윈도우 이벤트 로그 리페어 기법" 입니다. 어려운 것 별로 없구요... 제목만 보고 "와~"하시면 다 읽고 나셔서 "뭐야이게" 하실수도..(허무금지 ^^) 시작해 볼까요?


이벤트 로그 내부 구조 사~알짝 살펴보기
이벤트 로그 파일을 완전히 해부하려는 것은 아니구요~ 이벤트 로그 리페어에 필요한 간단한 내용만 알아보도록 하겠습니다. [그림 1]은 %systemdir%\system32\config\SecEvent.evt 파일을 WinHex로 오픈하여 살펴본 내용입니다. 부담없이 살펴보면 됩니다.

먼저 이벤트 로그 헤더 파일은 총 48bytes(0x30)로 구성되어 있습니다. 전체 48bytes 중 처음 4bytes와 마지막 4bytes는 헤더 사이즈입니다.

오프셋 4~7까지는 MS 공식 사이트에는 Reserved라고 표현되어 있습니다. 하지만 좀 더 자세히 읽어보면 아래와 같이 항상 0x4c664c65(LfLe) 값을 가지는 것을 알 수 있습니다. 이 부분은 이벤트 로그 파일의 시그너춰로도 활용할 수 있는 부분입니다.

오프셋 32~35까지의 데이터는 이벤트 로그 파일의 최대 크기를  나타냅니다.

오프셋 36~39까지는 Status byte입니다. 이벤트 로그 리페어에 있어 중요한 역할을 하는 필드이므로 잘 알아둘 필요가 있습니다. 이 필의 값은 보통 09, 0B, 08, 00 중 하나를 가지는데 09,0B는 현재 이 파일이 오픈되어 있음을 의미합니다. 이 필드의 값이 09또는 0B인 상황에서 Event Log API를 이용하는 프로그램(예 이벤트로그 뷰어)을 이용하여 파일을 오픈하려고 시도하면 에러가 발생합니다. 08과 00은 파일이 클로즈되어 있음을 의미합니다. 이벤트 로깅 서비스를 정상적으로 종료하는 경우 이 필드의 값은 08로 설정되며 이벤트 뷰어에서 "Save Log File As"를 이용하여 파일을 저장한 경우 새롭게 생성된 로그 파일의 이 필드 값은 00이 됩니다.

   

사용자 삽입 이미지

      [그림 1] 이벤트로그 헤더의 모양1


[그림 2]를 보면서 중요한 몇 몇 필드를 좀 더 살펴보도록 하겠습니다.

오프셋 16~31까지의 16bytes는 이벤트 로그 리페어에 있어 핵심적인 부분입니다. 각각 4bytes의 크기를 가지는 4개의 필드는 아래 그림에서 볼 수 있는 것처럼 "가장 오래된 이벤트 로그의 오프셋","다음 생성할 이벤트 로그의 오프셋","다음 생성할 이벤트 로그의 아이디", "가장 오랜된 이벤트 로그의 아이디"를 의미합니다. [그림 2]에 나타나 있는 데이터에 적용시켜 보면 가장 오래된 즉 가장 먼저 생성된 이벤트 로그의 오프셋은 헤더 바로 뒤에 존재하며(오프셋이 48byte), 새롭게 생성할 이벤트 로그의 오프셋 역시 헤더 바로 뒤에 존재함을 알 수 있습니다. 다시 말해 생성된 로그가 없다는 거죠. 예에서는 새롭게 생성될 이벤트 로그가 바로 최초로 생성되는 로그이며 로그 ID는 1이라는 것도 알 수 있습니다. 

사용자 삽입 이미지
     [그림 2] 이벤트 로그 헤더의 모양2


이벤트 로그 리페어와 관련하여 알아두어야 할 것이 하나 더 있는데, 바로 [그림 3]에 있는 floating footer입니다. 이러한 이름이 붙인 데에는 물론 이유가 있습니다. 말 그대로 이벤트 로그 파일의 끝을 나타내는 footer인데 로그가 새롭게 생성될 때마다 그 위치가 자꾸 변경되기 때문에(뒤로 밀리겠죠) floating이라는 형용사를 갖다 붙인거죠. floating footer는 40bytes 사이즈를 가집니다.  Floating 포인트 역시 헤더와 마찬가지로 처음 4bytes와 마지막 4bytes는 footer size값입니다. 오프셋 4byte 지점부터 보이는 11 11 11 11 ~ 44 44 44 44까지는 변하지 않는 값으로 저장매체 상에서 floating 포인터를  식별하는 시그너춰로 활용됩니다. 그 다음 오프셋 20byte~23byte, 23byte~27byte, 28byte~31byte, 32byte~35byte는 이벤트 로그 헤더 내 오프셋 16byte~31byte 사이에 존재하는 4개의 필드와 정확하게 일치하는 값을 가집니다. 만약 헤더와 footer에 존재하는 이 필드들의 값이 일치하는 않는 경우 이벤트 로그 API를 사용하는 프로그램들은 파일이 커럽트 된것으로 인지합니다.


사용자 삽입 이미지
   [그림 3] Floating footer


이벤트 로그는 왜 깨지는가?
이미 앞에서 다 알아본 내용이므로 간단히 정리해 보도록 하겠습니다. 흔히 "이벤트 로그가 깨졌다" 즉 커럽트되었다는 메시지는 다음과 같은 상황이 그 직접적인 원인이 됩니다.

* 이벤트 로그 헤더의 status byte 값이 00 또는 08이 아닌 경우
   - 이는 파일이 정상적으로 클로즈되지 않았음을 의미합니다. 예를들어 이벤트 로그 서비스가
     활성화되어 있는 상황에서 갑자기 시스템이 다운되었다든지 하는 경우겠죠.
     음..FBI 포렌직 매뉴얼에 보면 피의자를 컴퓨터를 조사하려는 경우 사진 팡~찍고
     바로 전원 플러그를 뽑도록 되어있습니다. 가능한 데이터가 변경되는 상황을 최소화하기
     위한 조치이죠. 증거 능력을 인정받는데도 중요하구요. 하지만 이런식으로 시스템을 종료
     시키면 이벤트 로그 헤더의 status byte 값이 변경되지 않아 이벤트 뷰어등 이벤트 로그
     API를 사용하는 프로그램을 이용하여 로그를 조사할 수가 없게 되는 것입니다.

     반대로 컴퓨터 전원을 끄지 않은 상태에서 증거를 수집하였을 때도 이런 상황이 발생할 수
     있습니다. 이벤트 로깅 기능이 활성화 되어 있는 상태에서 단순히 이벤트 파일만을 복사해  
     온다면 복사본의 staus byte값이 변경되지 않은 채로 남아있게 되는 거죠.

* 이벤트 로드 헤더안의 Oldest Event Log Offet, Next Event Log Offset, Next Event ID, Oldest
   Event ID 필드의 값과 floating footer안에 있는 대응되는 각 필드의 값이 일치하지 않는 경우
   -  새로운 이벤트가 발생하여 로그가 생성되었을 때를 생각해 보세요. 새로운 로그를 기록하는
      작업은 현재 floating footer의 위치에 로그 정보를 기록하고(A) 그 뒤편에 새로운 floating
      footer를 생성하는(B)는 두 단계로 이루어질 것입니다. 이 때 (A)작업을 마치고 (B)작업을
      시작하기 전에 프로그램이 비정상 종료된다든지, OS가 멈추다든지 하게 되면
      헤더 내의 정보와 footer 안의 정보가 일치하지 않게 될 것입니다. 이러한 경우가 위 상황이
      발생하는 되는 원인 중 하나가 됩니다.

간단한 실험을 통해서 이벤트 로그가 어떻게 커럽트 되는지 알아보고 커럽트 되었을 때의 이벤트 로그의 내부를 확인해 보도록 하겠습니다.

1. 먼저 이벤트 로깅 기능이 활성화 되어 있는지 확인합니다. 활성화 되어 있다면 AppEvent.evt 파일을 그냥 복사해 옵니다.

2. 복사된 AppEvent.evt를 이벤트 뷰어로 열어서 확인해 봅니다.  커럽트 되었다는 메시지만 뜨고 그 내용을 확인할 수가 없을 것입니다.

사용자 삽입 이미지
                [그림 4] 복사한 이벤트 로그 오픈 시 메시지

그럼 Hex 에디터를 이용하여 복사된 AppEvent.evt 파일을 열어서 중요 필드의 내용을 살펴보겠습니다.

사용자 삽입 이미지
[그림 5] 손상된 이벤트 로그 파일의 헤더와 floating footer 비교

대충 이벤트 로그 API가 어떠한 경우에 로그 파일이 손상되었다고 판단하는지 이해되시나요?
사실 맘 같아서 API를 추적하여 커럽트되었는지를 검사하는 루틴을 확인하고 싶지만 ^^; ... 혹 분석 해보시면 저도 알려주세용~


손상된 이벤트 로그 조사 방법
손상된 이벤트 로그를 조사하는 방법에는 로그 파일을 리페어 한 후에 조사하는 방법과 손상된 로그 파일을 수정하지 않고 레코드만 추출하여 조사하는 방법이 있습니다

리페어 후 조사하는 방법
 이벤트 로그 파일을 리페어 하는 방법은 매우 쉽습니다. 이벤트 로그 헤더 정보와 floating footer에 기록된 정보를 동기화 시켜주고 status byte 값을 08 또는 00으로 변경하면 됩니다. 그런 후에는 이벤트 로그 API를 이용한 툴들(이벤트 뷰어나 LogPaser 등)을 이용하여 조사하면 됩니다. 예제로 제공된 파일을 복구해 보도록 하겠습니다.

Hex 에디터로 손상된 이벤트 로그 파일을 오픈한 후 floating footer의 를 찾습니다. 찾을 때는 11 11 11 11 22 22 22 22  33 33 33 33 44 44 44 44 를 시그너춰로 하여 찾으면 되겠습니다. 찾은 후에는 floating footer에 기록된 Oldest Event Offset 부터 Oldest Event ID까지의 값을 복사한 후에 이벤트 로그 헤더의 해당 부분에 덮어 씌우면 됩니다. 그런 다음 status bytes의 값을 08 또는 00으로 수정하면 되는 것이죠. [그림 6]은 이러한 작업을 한 후의 모습입니다.

사용자 삽입 이미지
 [그림 6] 리페어 후 이벤트 로그 헤더와 floating footer의 모습

이제 이벤트 뷰어를 이용하여 파일을 열어보겠습니다.
사용자 삽입 이미지
 [그림 7] 복구 후 성공적으로 오픈한 모습

물론 자동화된 툴을 이용할 수도 있습니다. GrokEvtfixevt등이 대표적인 프로그램들입니다.
사용법은 단순하므로 생략합니다.
 
레코드를 직접 추출하여 조사하는 방법
앞에서 사용한 방법의 단점은 어찌되었든 커럽트된 이벤트 로그 파일을 조작해 한다는 것입니다. 포렌직 하시는 분들이 싫어할 만하죠. 그래서 같이 사용하는 방법 중에 이벤트 로그 API를 사용하지 않는 프로그램을 이용하여 수행합니다. 물론 하나씩 하나씩 분석해 볼 수도 있겠지만 현실적으로 거의 불가능하여 대부분은 Perl이나 Python같은 스크립트를 활용합니다.

(스크립트는 잠시 후에 추가하겠습니다. 밥먹으러 가야해서.. ^^;)

맺음말
간단한 팁 정도 수준의 내용이지만 알아두시면 매우 유용합니다. 로그 분석에 관련된 내용은 꾸준히 강좌를 올리도록 하겠습니다. 모두 행복한 하루 되세요.


Posted by zesrever

FSC2007 문제 풀이 두번째 글입니다. 첫번째 글은 여기를 참고해 주세요.

(딴지방지 : 빠르게 문제를 풀어야 한다는 목적에 충실한 더 좋은 솔루션이 있음을 저도
 압니다. 이 글은 그냥 많이 배워보자는 목적에서 작성되었습니다. ^^)
 
준비된 파일을 IDA로 열어 엔트리 포인트 부분을 살펴보았습니다. 군데군데 가비지 코드와 코드 퍼뮤테이션 같은 것들이 보입니다.

CODE:00401352 start           proc near
CODE:00401352
CODE:00401352 var_4           = dword ptr -4
CODE:00401352
CODE:00401352 ; FUNCTION CHUNK AT CODE:00401000 SIZE 0000000F BYTES
CODE:00401352
CODE:00401352     xor     ebx, ebx           ;디버거가 탐지된 경우 실행되지 않는
                                             ;부분. 매우 중요한 코드 인듯.
                                             ;일단 스킵
CODE:00401354     push    offset loc_40135A  ;코드 퍼뮤테이션 기법
CODE:00401359     retn                       ;push xxxx/retn => jmp xxxx
CODE:0040135A
CODE:0040135A loc_40135A:                    ; DATA XREF: start+2o

CODE:0040135A     push    eax                ;eax값을 스택에 push
CODE:0040135B     xor     eax, 427108h       ;귀여운 가비지 코드 ^^;
CODE:00401360     sbb     eax, edx           ;귀여운 가비지 코드 ^^;
CODE:00401362     xchg    eax, [esp+0]       ;결국 eax은 원래대로 복원됨
                                             ;따라서 위의 두 명령은 가비지

CODE:00401365     pop     ds:dword_402064    ;너는 뭐냐? 가비지냐?
                                             ;402064번지를 직접 가서 이 번지를
                                             ;참조하는 코드를 보면 여기 말고는
                                             ;없습니다. 일단 가비지인것 같군요.

CODE:0040136B     push    offset loc_401000  ;jmp loc_401000
CODE:00401370     retn
CODE:00401370 start           endp ; sp = -8

결국 엔트리 포인트에서 하는 일은 EBX의 값을 0으로 설정하고 loc_401000으로 점프하는 것입니다.
그럼 401000 번지를 향해 고고.

음 재미있는 코드입니다. 결국 JMP loc_40116D랑 같군요.
CODE:00401000 loc_401000:                       ; CODE XREF: start+1Ej
CODE:00401000                                   ; DATA XREF: start+19o
CODE:00401000     push    esp                   ;단순히 스택내 공간 확보가 목적
CODE:00401001     mov     [esp+4+var_4], eax    ;eax 값을 위에서 마련한 스택
                                                ;내 공간에 백업
CODE:00401004     mov     eax, offset loc_40116D
CODE:00401009     xchg    eax, [esp+4+var_4]    ;스택의 top 위치에 40116D저장
CODE:0040100C     retn    0                     ;결국 40116D로 점프



슬슬 귀찮아지기 시작하는 지점입니다. 어쨌든 40116D로 가보겠습니다.

CODE:0040116D loc_40116D:              ; DATA XREF: start-34Eo
CODE:0040116D     push    esp         
CODE:0040116E     mov     [esp], eax
CODE:00401171     mov     eax, offset sub_401186
CODE:00401176     xchg    eax, [esp]    ;스택의 top에 0x401186 저장
                                        ;뒤에 나오는 GetCommandLineA
                                        ;호출 후 복귀 주소입니다.
CODE:00401179     push    esp           
CODE:0040117A     mov     [esp], eax  
CODE:0040117D     mov     eax, offset loc_40137D
CODE:00401182     xchg    eax, [esp]    ;스택에 top에 loc_401037D 저장
CODE:00401185     retn                  ;결국 0x401037D로 점프.. 아띠..장난하나.

ㅠ.ㅠ;; 401037D로 가봅니다.
CODE:0040137D loc_40137D:          ; DATA XREF: CODE:0040117Do
CODE:0040137D     jmp     ds:GetCommandLineA


눈이 반짝반짝~ 뭔가 의미있는 코드인것 같습니다. GetCommandLineA가 호출됩니다. 이 프로그램호출 시 커맨드 라인을 줘야 하나 봅니다. GetCommandLineA를 이용하여 아규먼트를 읽은 다음에는 401186번지로 복귀합니다. 스택의 모양을 잘 그려보세요.  어쨌든 알아둘만한 패턴입니다.(PUSH return주소/JMP 함수시작주소 = CALL 함수 요넘의 변형입니다.)  

GetCommandLineA 호출 후 리턴주소인 0x401186번지로 이동해 보겠습니다. 하하.. 복잡해보이는군요. 이제는 디버거의 도움을 받아야 겠습니다. Ollydbg로 프로그램을 오픈한 후 401186번지에 브레이크 포인트를 설정하고서 실행시킨 다음 한 스텝씩 진행시켜 가며 코드를 분석했습니다. 먼저 00401187번지에서 EDX에 커맨드라인 아규먼트의 시작 주소를 복사해 넣는 것을 확인할 수 있군요. [그림 1]참고.
 

사용자 삽입 이미지
 [그림 1] 00401187번지에서 EDX에 커맨드라인 아규먼트의 시작 주소를 저장

그 다음 코드는 루프를 반복합니다. 변화되는 내용을 관찰하며 한두번 루프를 반복하다 보면 쉽게 이해할 수 있는 코드입니다. 복잡해 보이기만 했지 확인해보면 커맨드라인 아규먼트의 끝부분을 찾는 단순한 코드입니다. 끝부분 즉 널문자의 위치는 EDX 레지스터에 저장됩니다.
 
사용자 삽입 이미지
   [그림 2] 커맨드라인에서 NULL 문자의 위치를 찾음, 결과값은 EDX에 저장

다음 코드는 커맨드라인 아규먼트에서 실행파일 경로명을 제외한 나머지 부분을 체크하는 코드입니다. 코드를 찬찬이 뜯어보면 실행파일에 전달되는 아규먼트가 모두 4bytes이어야 한다는 사실을 알 수 있습니다. 즉 "FSC2007_Level2.exe XXXX" 형태가 되어야 하는 거죠.
사용자 삽입 이미지
[그림 3] 실행파일 경로를 제외한 아규먼트의 길이가 4bytes인지 검사

아규먼트를 주지 않은 채로 실행시켰기 때문에 정답과는 거리가 먼 곳으로 점프하겠군요. 그래서 004011E1에 브레이크 포인트를 설정하고 프로그램에 전달한 아규먼트로 "XXXX"를 지정한 후 다시 실행시켜 보았습니다.
사용자 삽입 이미지
                           [그림 4] 아규먼트 지정

4bytes 아규먼트가 주어진 경우 의미있는 코드가 실행되는군요. [그림 5]의 주석대로 EAX에 4bytes아규먼트와 0x5528566D 그리고 BH의 값을 XOR 한 결과 값을 저장합니다. 앞서 분석했던 TLS callback 함수에서 엔트리 포인트를 변경한 이유가 여기서 밝혀지는 군요. 디버거가 존재하는 경우 EBX에는 0이 아닌 다른 값이 저장되어 아래 코드의 계산 결과가 달라지겠습니다. 결국 엉뚱한 정답을 얻게 되겠네요.

사용자 삽입 이미지
[그림 5] EAX = 아규먼트(4bytes) XOR 0x5528566D XOR BH

이어지는 다음코드는 아래와 같습니다.
사용자 삽입 이미지
[그림 6]. EDX-6에 위차한 문자가 큰따옴표(")인지 검사.

이어지는 코드는 아래와 같습니다. 이 코드는 프로그램에 전달된 아규먼트 4bytes와 0x5528566D를 XOR한 결과 값이 6578652e인지를 비교합니다. 그렇다면 프로그램에 전달할 아규먼트는 0x5528566D와 0x6578652e를 XOR한 결과 값이 되면 되겠습니다. 휴~ 드디어 문제를 풀었군요.

사용자 삽입 이미지
[그림 7] 프로그램에 전달한 아규먼트 = 0x6578652e ^ 0x5528566d

위 값을 계산해보면 아규먼트는 0x30503343 이므로 리틀 엔디언임을 고려하면 C3P0 가 될 것이다.
ㅠ.ㅠ; 그럼 확인해 보겠습니다.

사용자 삽입 이미지
             [그림 8] 바로가기 등록

두근두근두근~~

사용자 삽입 이미지
               [그림 9] 유레카!!

레벨 2는 TLS callback 함수에 대한 처리만 잘 하면 그다지 어려울 것은 없어 보입니다. 실제 분석 시간도 1시간이 채 안걸렸는데요... FSC 사이트에 가보니 제일 먼저 푸신 분이 1시간 10분 정도 걸리셨다고 하는데... ^^; 그정도는 아닌 듯 싶습니다. 근데 이거 정답인가요... 알길이 없으니.. ㅠ.ㅠ;



이제 레벨 3 문제를 풀어볼까요..

Posted by zesrever

FSC2007 Level 2문제 풀이 첫번재 글 입니다. 풀이 내용이 길어서 2개의 글로 나누어 설명하겠습니다. 문제 파일은 두번째 글에 첨부되어 있습니다.

문제 : Level1과 마찬가지로 이메일 주소를 알아내야 합니다.

먼저 프로그램을 실행시켜 보겠습니다.

사용자 삽입 이미지

                   [그림 1] FSC_Level2.exe 실행 결과

TLS callback 분석
[그림 1]처럼 메시지박스 하나 뜨고는 아무런 이야기도 없군요. -.-;;  음... Level 1보다는 더 문제답게 보입니다. 먼저 PE 헤더 정보를 확인해 보겠습니다. 저는 stud_pe를 이용하였습니다. 뭐 아무거나 상관없습니다. PE 헤더를 살펴보다보니 TLS 섹션이 존재하는 군요. 이러한 경우 TLS callback 함수를 사용하는지 확인해봐야 합니다. TLS callback에 대해서는 여기를 참고하세요.

사용자 삽입 이미지
                           [그림 2] Stud_PE를 통해 TLS 섹션의 존재 확인

TLS callback 함수가 존재하는지 그 주소가 어떻게 되는지를 알아보기 위해서 IDA의 도움을 받아보겠습니다. IDA로 프로그램을 오픈한 후 Ctrl+E를 누르면 엔트리포인트 정보를 확인할 수 있습니다. IDA는 TLS callback을 분석할 줄 알기 때문에 엔트리포인트 윈도우에 그 주소가 나타납니다. 확인해보니 TLS callback 함수가 존재하는 군요!!! ^^; 음.. TLS callback 함수를 확인해 봐야겠죠.  함수의 시작 주소는 [그림 3]에서 확인할 수 있는 것처럼 0x004070A4 번지입니다.
 
사용자 삽입 이미지
                     [그림 3] IDA를 이용한 TLS callback 함수 주소 확인

해당 번지로 이동하여 코드를 살펴보았습니다.

UPX2:004070A4 TlsCallback_0   proc near  ; DATA XREF: UPX2:TlsCallbackso
UPX2:004070A4
UPX2:004070A4 arg_4           = dword ptr  8
UPX2:004070A4
UPX2:004070A4   cmp     [esp+arg_4], 1
UPX2:004070A9   jnz     short locret_4070C9
UPX2:004070AB   push    eax     ;eax값을 백업
UPX2:004070AC   mov     eax, large fs:18h     ;TIB의 주소를 eax에 저장
UPX2:004070B2   mov     eax, [eax+30h]        ;PEB의 주소를 eax에 저장
UPX2:004070B5   movzx   eax, word ptr [eax+2] ;BeingDebugged 값을 eax에 저장
UPX2:004070B9   cmp     eax, 0                ;BegingDebugged 값이 0인지 확인
UPX2:004070BC   setz    al                    ;0이면(디버깅 중이 아니면) a
                                              ;l의 값을 1로 설정
UPX2:004070BF   imul    eax, 8                ;eax = eax*8
UPX2:004070C2   sub     byte ptr ds:loc_4063BC+1, al
                ;loc_4063BC+1 번지에 저장되어 있는 값에서 al에 저장되어 있는
                ;값을 뺀 후 저장
                ;loc_4063BC에는 "jmp  near ptr word_40135A"가 위치함
                ;JMP가 1byte이고 주소값이 4bytes이므로 결국 JMP할 지점을
                ;수정하게 됨
UPX2:004070C8   pop     eax     ;eax값을 복구
UPX2:004070C9
UPX2:004070C9 locret_4070C9:           ; CODE XREF: TlsCallback_0+5j
UPX2:004070C9                 retn
UPX2:004070C9 TlsCallback_0   endp


파란색으로 표시된 코드 부분은 전형적인 안티 리버싱 기법이죠. PEB에 존재하는 BeingDebugged 값을 확인하여 디버거를 탐지하는 기법입니다. 자세한 내용은 여기를 참고하세요.

문제에서 TLS callback으로 등록된 함수는 PEB의 멤버 중 BeingDebugged 값을 확인하는 방법으로 디버거를 탐지하고 디버거가 탐지되는 경우 JMP할 곳의 주소를 변경하는 함수입니다. 디버거가 탐지된다면 loc_40135A로 디버거가 탐지되지 않는다면 loc_401352번지로 점프합니다. 아직 풀리지 않은 문제는 조작 대상이 되는 jmp 코드의 의미입니다. 이 부분은 잠시 후에 다시 보기로 하겠습니다.

분석을 더 진행하기 전에 한가지 더 생각해봐야 할 사실이 있습니다. 섹션 이름에 UPX라는 문자열이 확인된다는 점인데요, 아마도 UPX로 패킹했을 것이라는 추측이 가능합니다. PEiD를 사용할 필요없이 UPX를 이용하여 바로 확인해 보았습니다.

사용자 삽입 이미지
        [그림 4]UPX로 패킹되었는지 확인

예상대로 UPX로 패킹되었군요. 이건 함정을 파놓은 것이라 생각됩니다. 그도 그럴 것이 UPX는 단순히 실행파일 압축을 목적으로 한 패커로 안티리버싱 테크닉 같은 것은 구현되어 있지 않습니다. 게다가 UPX 프로그램 자체에 UPX를 언패킹하는 기능이 포함되어 있죠. TLS callback 함수의 존재 여부를 확인하지않고 PEiD등의 툴로 문제 파일을 조사하였거나 IDA로 열어서 본 경우 약간의 경험을 가지고 있는 리버서라면 누구나 UPX로 패킹되었다는 사실을 알 수 있었을 테고 UPX를 이용하여 간단하게 언패킹 하였을 것입니다. 이런식으로 언패킹을 하게되면 패킹되기 전의 실행 파일을 얻을 수 있을테지만 TLS callback 함수를 날려버리게 됩니다. 만약 TLS callback 함수에 프로그램의 실행과 관련된 중요한 코드가 포함되어 있다면 어떻게 되겠습니까? 그야말로 뻘짓을 하게 됩니다. 아무리 분석해도 정답에 가까이 가기는 힘들겠죠. 

결국 TLS callback에서 하는 일이 무엇인가가 중요한 문제인 것 같습니다. TLS callback 함수가 디버거의 존재 여부에 따라  4063BC번지에 존재하는 jmp 문의 오퍼런드(절대주소)를 변경한다는 것 까지는 앞에서 알아봤습니다. 그럼 조작되는 jmp문의 역할을 알아볼 차례인 것 같습니다. 먼저 해당 코드 부분을 살펴보았습니다.

UPX1:004063BB loc_4063BB:           ; CODE XREF: start+DDj
UPX1:004063BB               popa
UPX1:004063BC
UPX1:004063BC loc_4063BC:           ; DATA XREF: TlsCallback_0+1Ew
UPX1:004063BC               jmp     near ptr word_40135A
UPX1:004063BC start         endp

대충 감이 잡히는 군요. UPX의 경우 별다른 조작을 가하지 않았다면 UPX1이라는 이름의 섹션에 마지막에 존재하는 jmp 명령어로 점프하는 곳이 OEP 입니다. 게다가 위에 POPA 까지보이니 40135A가 OEP라는 것이 거의 맞는 것 같습니다. 하지만 이건 어디까지나 추측일 뿐이고, 실제로 004063BC에 위치한 jmp문이 실행되지 않는다면 이 역시 트릭이겠죠. 디버깅을 통해서 확인해 보겠습니다. 만사불여튼튼... ^^; 매 단계마다 검증 과정을 빼놓는다면 트릭에 빠질 확률이 높습니다.  검증을 위해 004063BC에 브레이크 포인트를 설정한 후 실행시켜 보았습니다. 

사용자 삽입 이미지
       [그림 5]004063BC번지에 브레이크 포인트 설정


두세번 F9(Run)를 누르면 004063BC 번지에 설정한 브레이크 포인트가 히트됨을 알 수 있습니다. 트릭은 아니군요. ^^;

사용자 삽입 이미지
         [그림 6] 004063BC에서 브레이크 포인트 히트

여기까지 정리해보겠습니다. 

* FSC_Level2.exe는 UPX로 패킹한 후 TLS 섹션이 추가되었으며 TLS callback 함수가 존재한다.
* TLS callback 함수는 디버거의 존재 여부에 따라 엔트리 포인트 값을 변경하는 기능을 가지고 있다.
* 디버거가 존재하지 않는 경우의 엔트리 포인트는 00401352번지이다.


UPX로 언패킹 후 엔트리 포인트 수정
음.. 대충 해결 방법이 보이는 군요. 그냥 UPX로 언패킹한 후 엔트리 포인트를 00401352 번지로 변경하면 되겠습니다. 
사용자 삽입 이미지
         [그림 7]UPX를 이용하여 FSC_Level2.exe를 언패킹


Stud_PE를 이용하여 언패킹된 파일을 오픈한 후 엔트리포인트를 0000135A에서 00001352로 번경하였습니다.

사용자 삽입 이미지
                     
   [그림 8]언패킹한 후 엔트리 포인트 변경

음 이제서야 분석할 준비가 끝났습니다. ^^; 사실 설명이 좀 길어서 그렇지 실제 분석에 소요된 시간은 10분 남짓입니다. 언패킹한 후 조작한 파일을 실행시켜보면 [그림 1]과 동일한 결과를 얻을 수 있습니다. 그럼 문제를 계속 분석해 보도록 하겠습니다.

다음글로 이어집니다.

Posted by zesrever

어제 동호회 사람들과 워크샵을 다녀온 뒤라 좀 피곤합니다. 간만에 술을 뽀지게 마셔서.. 뒹글뒹글 하던 차에 좀 뒤 늦은 감이 있지만 아는 분이 시간 때우기(?) 좋다고해서 해봤습니다. F-Secure Challenge 공식 사이트에 공개되어 있습니다. 리버싱을 처음 공부하시는 분들은 솔루션 보고 따라해보시는 것도 공부에 도움이 될 듯 싶습니다.

덧붙임 :  이런 챌린지들에 대한 솔루션은 매우 다양합니다. 이 글에서 언급하고 있는 방법보다 더 좋은 방법들이 있을 수도 있겠습니다. Level1의 경우 사실 문제 푸는데는 초보자라 하더라도 1~2분 정도면 충분할 정도로 쉽습니다. 하지만 문제 푸는 것 자체보다는 과정을 이해하시는 것이 더 좋을 것 같아 좀 풀어서 설명합니다. 어차피 시간 지난 문제인데 빨리 풀어내는 방법에 초점을 맞출 필요는 없을 것 같아서요.

Level 1:  제공되는 바이너리에 이메일 주소가 숨겨져 있습니다. 이 이메일 주소를 찾아서 이메일을 보내면 다음 레벨로 가기 위한 방법을 메일로 보내줬답니다.. ^^; 물론 대회는 이미 종료된 관계로 실제 메일을 보내도 응답 메일이 올리는 만무하죠. 이메일만 찾아보면 되겠습니다.


문제로 출제된 프로그램을 실행시켜 보니 아래와 같이 Key를 입력하라고 나옵니다.

사용자 삽입 이미지
            [그림 1] FSC 2007 Level 1. 문제 화면

아마도 Key를 입력하면 이메일 주소가 나오나 봅니다.  아무 문자열이나 입력해 보았습니다. 물론 맞을리 없겠죠. 하지만 Key값을 비교한 후 정답이 되는 키값과 다르면 출력되는 메시지는 확인할 수 있겠죠. 메시지를 확인하면 메시지를 참조하는 부분을 찾을 수 있을테고 그 근처를 조사하다보면 key값이 올바른지 검사하는 부분을 찾을 수 있을 겁니다. [그림2]는 잘못된 키 값을 입력한 결과화면 입니다.
사용자 삽입 이미지
            [그림 2] 잘못된 키값을 입력한 결과

사랑스러운 그녀, IDA의 도움을 받아보겠습니다. IDA로 문제 파일을 오픈한 후 String 윈도우에서 "Sorry, this key is not valid!"라는 문자열을 찾아보았습니다.
사용자 삽입 이미지
        [그림 3] String 윈도우에서 메시지 확인

찾은 문자열을 더블클릭합니다.  위 문자열은 ".rdata" 섹션의 690020c0 번지에 저장되어 있음을 알 수 있습니다. 우측에 주석처리되어 있는 크로스 레퍼런스를 확인해보면 0x690010F3 번지에 있는 코드가 이 문자열을 참조하는 것을 알 수 있습니다. 필시 [그림2]와 같은 메시지를 출력하는 부분이겠죠.
사용자 삽입 이미지
         [그림 4] .rdata 섹션에서 확인한 문자열

크로스레펀런스를 더블 클릭하여 해당 코드로 이동하여 보겠습니다.
사용자 삽입 이미지
  [그림 5] Sorry 메시지 출력 부분

역시 예상대로 printf를 이용하여 메시지를 출력하고 있습니다. 우측 주석을 보면 _main+D4에서 이 코드로 jump해 온 것을 알 수 있습니다. 문제의 성격 상 키값을 비교하여 그 결과에 따라서 이 코드로 점프를 해 왔을 것이란 추측이 가능합니다. 비교문의 경우 그래프로 보면 좀 더 편하게 코드를 분석할 수 있습니다. IDA에서 텍스트 뷰와 그래프 뷰 모드 간 전환은 [스페이스바]를 이용하면 됩니다. 스페이스바를 눌러 그래프 뷰로 전환하여 코드를 살펴보았습니다.

사용자 삽입 이미지
 [그림 6] 키값 비교 부분을 그래프 뷰로 확인한 결과

한 눈에 잘 들어오네요. stricmp로 문자열을 비교하고 그 결과에 따라(test eax, eax)  적절한 메시지를 출력하는 분기가 이루어짐을 확인할 수 있습니다. 여기부터는 각자의 취향이나 목적에 따라 솔루션이 달라지겠습니다. 쉽게 생각할 수 있는 몇 가지 솔루션으로 나누어 확인해 보겠습니다.

(솔루션 1) 가장 쉽고 정확한 솔루션 : 디버깅을 통해 비교되는 문자열 확인하기
stricmp를 이용하여 문자열을 비교한다는 사실을 위에서 확인했습니다. stricmp(정상적인키값,사용자입력값) 이런 식으로 비교하겠죠. 따라서 stricmp에 브레이크 포인트를 설정하고 파라미터로 넘어오는 값을 확인하면 키 값을 쉽게 알아낼 수 있을 것입니다. 문자열 비교하는 지점을 정확하게 알 수가 없다면 브레이크 포인트를 IAT에 설정해야겠지만 문제의 경우 문자열 비교 지점을 정확하게 알고 있으므로 690010c9 번지에 직접 브레이크 포인트를 설정하면 됩니다.

사용자 삽입 이미지
  [그림 7] 문자열 비교 지점에 브레이크 포인트를 설정한 후 실행시켜 파라미터 확인

Ollydbg의 스택 윈도우를 살펴보면 첫번째 파라미터는 입력한 문자열 "zesrever"를 가르키고 있으며 두번째 파라미터는 키값 "Asm07REC"를 가르키고 있음을 확인할 수 있습니다. 정답인지 확인해 보겠습니다.

사용자 삽입 이미지
            [그림 8] 정답 화면

음.. 이메일 주소가 나왔습니다. 메일을 보내면 다음 문제가 온다고 해서 바보같지만 혹시나 하는 마음에 보내봐도 역시 안오는군요. ^^; 정답인지 확인할 길은 없습니다만... 별다른 코드가 없는 것으로 봐선 확실한 것 같습니다.

(솔루션 2) 문자열 비교 결과를 조작하여 성공했을 경우 출력되는 메시지 확인(코드 패치)
또 다른 방법은 stricmp후 비교하는 부분의 코드를 수정하는 것입니다. 키값으로 아무 내용이나 입력하고 비교문에서 조건을 반대로 주어 성공했을 경우의 메시지가 출력되도록 하는 것이죠. 아래 [그림 9]에서 처럼 690010D4 번지로 이동한 후 [스페이스바]를 눌러 어셈블 창을 띄운 후 JNZ를 JZ로 변경합니다.

사용자 삽입 이미지

              [그림 9] JNZ를 JZ로 변경

[그림 10]은 코드 패치 후 실행 시킨 결과입니다. 역시 동일한 메시지가 출력되는 군요.

사용자 삽입 이미지
             [그림 10] 코드 패치 후 실행 결과


(솔루션 3) 키값을 직접 계산하기
 키 생성 알고리즘이나 키 생성 방법등을 리버싱하는 것도 한 방법입니다. 물론 이 문제의 경우 이 솔루션은 앞의 솔루션들에 비하면 그다지 효과적이라 할 수는 없습니다. 그래도 정답도 검증할 겸해서 살펴보도록 하죠. 먼저 문자열 비교하는 부분을 살펴보겠습니다.

.text:690010BF     push    offset byte_69003310 ; char *
.text:690010C4     push    offset byte_690031A0 ; char *
.text:690010C9     call    ds:_stricmp


stricmp에 파라미터로 전달되는 두 개의 포인터는 모두 전역변수를 가르키는 것을 쉽게 알 수 있습니다. 둘 중 하나는 키 값을 저장하는 변수일테고 다른 하나는 입력받은 키 값을 저장하는 변수일 것입니다. 아래의 코드를 보면 0x690031A0는 입력받은 키 값을 저장하는 변수의 주소임을 알 수 있습니다.

.text:6900105D     push    offset byte_690031A0
.text:69001062     push    offset aS       ; "%s"
.text:69001067     call    ds:scanf

그렇다면 0x69003310번지가 키 값을 저장하는 변수의 주소임을 알 수 있습니다. 그럼 이 번지에 어떠한 값이 저장되는지를 추적하면 키 값을 알아낼 수 있겠군요. 관련 코드는 어렵지 않게 찾을 수 있습니다. 아래 부분이 키값을 생성하는 코드입니다.

.text:6900106D  movsx   ecx, byte ptr aAssembly2007Re+22h
.text:69001074  movsx   edx, byte ptr aAssembly2007Re+16h
.text:6900107B  movsx   eax, byte ptr aAssembly2007Re+0Eh
.text:69001082  mov     edi, ds:sprintf
.text:69001088  push    ecx
.text:69001089  movsx   ecx, byte ptr aAssembly2007Re+0Ch
.text:69001090  push    edx
.text:69001091  movsx   edx, byte ptr aAssembly2007Re+0Bh
.text:69001098  push    eax
.text:69001099  movsx   eax, byte ptr aAssembly2007Re+4
.text:690010A0  push    ecx
.text:690010A1  movsx   ecx, byte ptr aAssembly2007Re+1
.text:690010A8  push    edx
.text:690010A9  movsx   edx, byte ptr aAssembly2007Re ; "Assembly 2007 Reverse-Engineering Chall"...
.text:690010B0  push    eax
.text:690010B1  push    ecx
.text:690010B2  push    edx
.text:690010B3  push    offset aCCCCCCCC ; "%c%c%c%c%c%c%c%c"
.text:690010B8  push    offset byte_69003310 ; char *
.text:690010BD  call    edi ; sprintf

위 코드를 보기 좋게 정리해 보겠습니다.
.text:6900106D  movsx   ecx, byte ptr aAssembly2007Re+22h
.text:69001088  push    ecx
.text:69001074  movsx   edx, byte ptr aAssembly2007Re+16h
.text:69001090  push    edx
.text:6900107B  movsx   eax, byte ptr aAssembly2007Re+0Eh
.text:69001098  push    eax
.text:69001089  movsx   ecx, byte ptr aAssembly2007Re+0Ch
.text:690010A0  push    ecx
.text:69001091  movsx   edx, byte ptr aAssembly2007Re+0Bh
.text:690010A8  push    edx
.text:69001099  movsx   eax, byte ptr aAssembly2007Re+4
.text:690010B0  push    eax
.text:690010A1  movsx   ecx, byte ptr aAssembly2007Re+1
.text:690010B1  push    ecx
.text:690010A9  movsx   edx, byte ptr aAssembly2007Re
.text:690010B2  push    edx
.text:69001082  mov     edi, ds:sprintf
.text:690010B3  push    offset aCCCCCCCC ; "%c%c%c%c%c%c%c%c"
.text:690010B8  push    offset byte_69003310 ; char *
.text:690010BD  call    edi ; sprintf


한결 보기 편하군요. 코드내에서 aAssembly2007Re라고 이름 붙여진 곳에 가보면 아래에서 확인할 수 있는 것처럼 "Assembly 2007 Reverse-Engineering Challenge - Level 1"이라는 문자열이 저장되어 있습니다.
.data:69003020 aAssembly2007Re db 'Assembly 2007 Reverse-Engineering Challenge - Level 1',0Ah

따라서 위의 코드는 위 문자열의 시작 주소 +0, +1, +4, +0xB, + 0xC, +0xE, +0x16, +0x22,에 해당하는 문자들을 스택에 push하고, 문자열 "%c%c%c%c%c%c%c%c"의 주소값과 키값을저장할 변수의 주소를 스택에 push한 후  sprintf를 호출하고 있습니다. 따라서 위의 긴 코드는 아래와 같이 정리할 수 있을 것입니다.

char key[10];
banner = "Assembly 2007 Reverse-Engineering Challenge - Level 1";
sprintf(key,"%c%c%c%c%c%c%c%c",banner[0],banner[1],banner[4],banner[11],banner[12], banner[14],banner[22],banner[34]);

그럼 key값은 문자열 "Assembly 2007 Reverse-Engineering Challenge - Level 1" 중에서 파란 색으로 표시된 문자들을 모아놓은 것이 되겠군요. 앞서 확인한 결과와 일치합니다.



간단한 문제 하나가지고 잘 울궈먹은 것 같습니다. ^^;; 이제 Level 2로 가보실까요..



Posted by zesrever

개요
Junk Code(이하 정크 코드)는 Garbage Code(이하 가비지 코드)와는 약간 다른 의미로 사용됩니다. 가비지 코드는 "진짜 코드"를 감추기 위해서 삽입된 무의미한 코드를 일컫는 말로 리버서 즉 사람을 속여 리버싱을 하는 시간을 더디게 하는 것이 주 목적입니다. 반면 정크 코드는 리버싱을 속도를 더디게 할 목적으로 삽입된 코드라는 점을 같지만 디스어셈블러를 속인다는 점이 가비지 코드와 다릅니다. 이번에 다룰 내용은 바로 이 정크 코드와 가비지 코드를 이용한 안티 리버싱 기법입니다.

가비지 코드를 이용한 안티 리버싱 
정크 코드나 가비지 코드는 다분히 트릭의 성격이 강합니다. 별다른 원리라고 부를 만한게 없다는 것이죠. 예제를 보면 금방 이해갈 것이라고 생각합니다. 먼저 가비지 코드부터 살펴보겠습니다. 앞서 이야기한 대로 가비지 코드는 "진짜 코드"를 보호하기 위해 여기저기 맘대로 삽입된 무의미한 코드입니다. 아래의 예를 봐주세요.

보호할 코드 (PEB.IsBeingDebugged를 이용한 디버거 탐지)

  MOV EAX, DWORD PTR FS:[18h]
  MOV EAX,  DWORD PTR DS:[EAX+30h]
  MOVZX EAX, BYTE PTR DS:[EAX+2h]
  TEST EAX, EAX
  JNZ @DebuggerFound


보호할 코드 + 가비지 코드
  XOR EBX, EBX
  MOV EBX, FFEEFFEEh
  SHR EBX, 4
  ADD ESI, EBX
  MOV EAX, DWORD PTR FS:[18h]
  MOV ECX, 3204F83Eh
  SUB ECX, EBX
  PUSH offset @JumpHere
  ADD ECX, ESI
  LEA EDX, [ESI+ECX]
  MOV EAX,  DWORD PTR DS:[EAX+30h]
  RETN
  SHR EDX, 5
  MOV ESI, EDX
 
@JumpHere:
  MOVZX EAX, BYTE PTR DS:[EAX+2h]
  LEA  EBX, [EAX+ECX]
  XOR  ECX, ECX
  TEST EAX, EAX
  PUSH ESI
  LEA  ESI, [ESP+0Ch]
  POP  EDI
  PUSH EAX
  JNZ @DebuggerFound


^^; 숨은 그림 찾기 같지 않습니까? 진짜 코드를 아무런 의미없는 코드 사이에 숨겨놓았습니다. 가비지 코드가 그럴 듯하면 할수록 리버서는 엉뚱한 것을 분석하느라 시간을 보내게 되겠죠. 이런 코드는 분석하는 제너럴한 테크닉은 없습니다. 예제 코드의 경우라면 FS:[18h]의 값이 EAX에 저장되었다는 사실에 주목하여 EAX 위주로 코드를 찾아가다보면 어느 정도 윤곽을 잡을 수 있습니다. 어쨌든 아는 만큼 밖에 안보이는 형태라 리버서들을 괴롭히는 트릭입니다.

Junk Code를 이용한 안티 리버싱
다음 예제코드를 봐주세요.

main()
{
   __asm
  {
      jmp here+1 ;
     here:
      __emit 0xe9     ;__emit은 뒤에 오는 바이트를 그대로 코드에 포함시킬때 사용
                      ;0xe9은 jmp를 나타내는 opcode

     mov eax, 1 ;
 }
}
위 코드는 실제로 하는 일이 mov eax, 1을 수행하는 것입니다. 일단 컴파일 한 다음 디스어셈블러와 디버거로 열어서 확인해 보도록 하겠습니다. 먼저 dumpbin으로 디스어셈블한 결과입니다.

C:\zesrever\lab\dumpbin /disasm junk.exe
 _main:
  00401000: 55                 push        ebp
  00401001: 8B EC              mov         ebp,esp
  00401003: 53                 push        ebx
  00401004: 56                 push        esi
  00401005: 57                 push        edi
  00401006: E9 01 00 00 00     jmp         0040100C
  0040100B: E9 B8 01 00 00     jmp         004011C8
  00401010: 00 5F 5E           add         byte ptr [edi+5Eh],bl
  00401013: 5B                 pop         ebx
  00401014: 5D                 pop         ebp
  00401015: C3                 ret

   (이하 생략)

어... main 함수안에 mov eax, 1이 안보이네요~ 00401006번지를 잘 살펴보면 0040100C 번지로 점프한다는 것을 확인할 수 있죠. 그런데 그 아래 코드를 보면 0040100B번지에서 시작합니다. 그렇다면 0040100B번지에 있는 E9는 아무런 의미가 없는 바이트가 되겠죠. 따라서 제대로 분석하려면 E9은 버리고 B8을 명령어로 해석해야 합니다. dumpbin과 같이 플로우 분석을 하지 않는 단순한 디스어셈블러들은 이런 사실을 알리가 없습니다. 그래서 위와 같은 결과가 나오는 것이죠... 그럼 ollydbg는 어떨까요?

사용자 삽입 이미지

ollydbg는 좀 똑똑하네요. ^^; 하지만 플로우 분석을 해 주는 이런 툴들에 전적으로 의존하는 것보다는 코드를 주의깊에 살펴보는 것이 필요하겠습니다. 혹시 ollydbg가 아래와 같은 모양을 보인다면 (간혹 그런 케이스도 있더라구요) 바이너리 편집을 하면 합니다.
사용자 삽입 이미지

위와 같이 편집할 부분을 선택하고 Ctrl+E(Edit)를 누릅니다.
사용자 삽입 이미지

그런 다음 정크 코드로 의심되는 부분을 NOP(0x90)으로 변경해 주면 됩니다. 아래는 이렇게 해서 변경한 결과입니다.

사용자 삽입 이미지

 
IDA는 이런 간단한 트릭에는 잘 속지 않습니다. 그나마 다행이죠. 이제 정크 코드를 이용한 안티 리버싱 기법이 이해되시나요?

맺음말
가비지 코드나 정크 코드는 매우 단순하지만 효과적인 안티 리버싱 기법입니다. 이 글을 보시는 분 중 프로그램 잘 하시는 분 계시면 가비지 코드 제거 플러인 같은 것 개발해주실래요? ^^;; 저는 하다가 머리아파서 포기...(점점 살리에르가 되어가는 기분입니다. 원래 모짜르트인 적도 없었기는 하지만) 즐핵~  

Posted by zesrever

개요
윈도우는 프로그램의 디버깅을 돕기 위한 많은 기능들을 가지고 있습니다. 이러한 기능의 사용을 제어할 수 있는 방법 중 하나가 Process Environment Block(이하 PEB)에 저장되어 있는 NtGlobalFlag 값을 조정하는 것입니다. 예를 들자면 Heap 오버플로우가 발생하는지를 체크하기 위해서 NtGlbalFlag의 값을 0x10으로 조정하는 식입니다. 정상적인 환경에서 프로그램이 실행되는 경우 보통은 이러한 디버깅 지원 기능들을 사용하지 않는 것이  일반적입니다. 하지만 디버깅을 하는 경우라면 이야기가 달라지겠죠. 다시 말해 디버거가 붙어 있을 때와 그렇지 않을 때 PEB.NtGlobalFlag의 값이 다르다는 이야기입니다. 이러한 사실은 디버거를 탐지하는데 이용됩니다. 이 글에서 알아볼 내용이 이에 대한 것입니다.

PEB.NtGlobalFlag
앞서 NtGlbalFlag의 역할에 대해서 간단히 알아보았습니다.  이 글에서는 NtGlobalFlag에 포함된 각 플래그의 기능에 대해서 설명하는 대신 디버깅을 할 때와 하지 않을 때 NtGlobalFlag 값은 어떤 차이가 있는지를 먼저 살펴볼 것입니다. NtGlboalFlag에 포함된 각 플래그의 기능에 대해서 궁금하신 분은  MS Technet을 참고하시기 바랍니다.

먼저 테스트에 사용할 간단한 코드를 작성해야 하겠습니다. C/C++로 작성하면 보다 읽기 좋겠지만 그냥 간단하게(?) 어셈블리로 작성하도록 하죠. 일단 PEB의 구조부터 살펴보겠습니다. 어렵게 헤더 파일을 헤집고 다닐 필요없이 WinDbg로 간단히 살펴보죠. (WinDbg로 아무 실행 파일이나 하나 연다음 아래처럼 명령어를 내리면 됩니다. 명령어가 생소하신 분은 RTFM!! Windbg 도움말을 참고하세요. 인터넷 찾아 헤맬필요 없습니다.)

0:000> dt _PEB
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
     (중간 생략)
   +0x064 NumberOfProcessors : Uint4B
   +0x068 NtGlobalFlag     : Uint4B
   +0x070 CriticalSectionTimeout : _LARGE_INTEGER
     (이하 생략)


위의 결과로부터 우리가 읽어야 하는 NtGlobalFlag는 PEB 시작 위치에서 0x68만큼 떨어진 곳에 위치하고 있는 4bytes 크기의 데이터라는 사실을 알 수 있습니다. 그럼 PEB의 시작 위치를 구하는 방법만 알면 되겠습니다. PEB의 시작 위치는 TEB(Thread Environment Block)에서 찾을 수 있습니다.그럼 TEB의 구조도 살펴볼까요?

0:000> dt _TEB
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
    (이하 생략)


음.. PEB의 주소는 TEB+0x30에 저장되어 있군요. ^^; 그럼 TEB의 주소는? TEB는 FS가 가르키는 세그먼트의 시작점에 위치합니다. 다시 말해 FS:0이 바로 TEB의 시작 주소입니다. 따라서 PEB의 시작 주소는 FS:[0x30]이 되는거죠. 그럼 NtGlobalFlag의 주소는? FS:[0x30]+0x68 이 됩니다. 이상의 사전 지식을 바탕으로 작성한 코드가 아래에 준비되어 있습니다.

[예제코드 1] NtGlobalFlag 값을 읽는 코드. ReadGlobalFlag.asm

   .586
      .model flat, stdcall
      option casemap :none   ; case sensitive

      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc

      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib
 .data
      MainCaption db "The Value of PEB.NtGlobalFlag", 0h
      fmtstring db "NtGlobalFlag : %x",0h
      GlobalFlag dd 0
 .code
 
start:
  ASSUME FS:NOTHING
  MOV  EAX, FS:[30h] ;EAX에 PEB의 주소를 저장
  MOV  EAX, [EAX+68h] ;EAX+0x68은 PEB.NtGlobalFlag의 주소
             ;따라서 EAX에는 PEB.NtGlobalFlag값이 저장됨
 
 
  ;메시지 박스로 출력할 Text 생성
  PUSH EAX        
  PUSH offset fmtstring
  PUSH offset GlobalFlag
  CALL wsprintfA
 
  PUSH 40h
  PUSH offset MainCaption
  PUSH offset GlobalFlag
  PUSH 0
  CALL MessageBox
  PUSH 0
  CALL ExitProcess
end start


자..이제 실행시켜 볼까요? 테스트는 Ollydbg와 Windbg를 이용하였습니다.  각각의 결과는 아래와 같습니다.

사용자 삽입 이미지
                                        [그림 1] Normal 환경에서의 NtGlobalFlag값

정상적인 경우 즉 디버거를 붙이지 않은 경우 NtGlobalFlag 값은 0임을 확인할 수 있습니다.


사용자 삽입 이미지
        [그림 2] Ollydbg로 디버깅 하였을 때의 NtGlobalFlag 값

Ollydbg로 디버깅하였을 경우 NtGlobalFlag 값은 0x70입니다.


사용자 삽입 이미지
                 [그림 3] Windbg로 디버깅 하였을 때의 NtGlobalFlag 값

Windbg의 경우도 Ollydbg와 마찬가지로 0x70값을 가지는 군요.

위의 실험으로 확인된 것처럼 디버거를 사용하는 경우 일반적으로 NtGlobalFlag는 0이 아닌 다른 값(0x70)으로 설정됩니다. 참고로 0x70은 아래와 같은 플래그가 설정된 결과입니다.

FLG_HEAP_ENABLE_TAIL_CHECK (Heap Tail Checking)               : 0x10
FLG_HEAP_ENABLE_FREE_CHECK (Heap Free Checking)            : 0x20
FLG_HEAP_VALIDATE_PARAMETERS (Heap Parameter Checking) : 0x40

위의 각 플래그들은 heap과 관련한 디버깅에 필요한 옵션입니다.

PEB.NtGlobalFlag를 이용한 디버거 탐지
NtGlobalFlag를 이용하여 디버거를 탐지하는 로직은 단순합니다. 그냥 PEB.NtGlobalFlag 값을 읽어 이 값이 0인지를 테스트해 보면 되는 거죠. 사실 [예제코드 1]을 조금만 손 보면 됩니다. 아래 수정된 코드를 참고하세요.

[예제코드 2] PEB.NtGlobalFlag를 이용한 Debugger 탐지

(앞부분 생략)
.data
      MainCaption db "Debugger Detection", 0h
      TextDebuggerFound db "Debugger Found!!",0h
      TextDebuggerNotFound db "Debugger Not Found!",0h
 .code
 
start:
  ASSUME FS:NOTHING
  MOV  EAX, FS:[30h] ;EAX에 PEB의 주소를 저장
  MOV  EAX, [EAX+68h] ;EAX+0x68은 PEB.NtGlobalFlag의 주소
             ;따라서 EAX에는 PEB.NtGlobalFlag값이 저장됨
 
 
  TEST EAX, EAX
  JZ  @DebuggerNotFound
 
 @DebuggerFound :
  PUSH 30h
  PUSH offset MainCaption
  PUSH offset TextDebuggerFound
  PUSH 0
  CALL MessageBox
  PUSH 0
  CALL ExitProcess

 @DebuggerNotFound:
  PUSH 30h
  PUSH offset MainCaption
  PUSH offset TextDebuggerNotFound
  PUSH 0
  CALL MessageBox
  PUSH 0
  CALL ExitProcess
end start


다음은 위의 코드를 ollydbg 안에서 실행한 결과입니다.

사용자 삽입 이미지

       [그림 4] ollydbg에서 실행한 화면

PEB.NtGlobalFlag를 이용한 디버거 탐지 우회
디버거 탐지 코드를 식별할 수 있는 경우라면 NtGlobalFlag의 값을 테스트한 후 조건 분기하는 부분에서 코드를 수정하는 방법을 사용할 수 있습니다. 아래는 예제코드에 사용된 코드를 디버거에서 수정한 예입니다. JZ를 JNZ로 변경하였습니다.

사용자 삽입 이미지
      [그림 5] PEB.NtGlobalFlag를 이용한 디버거 탐지 우회-1

하지만 대부분의 이러한 코드들은 가비지와 섞여 있고 여러가지 트릭들이 사용되어 한눈에 식별되지 않은 경우가 많을 뿐더러, 여러 번 체크할 수 있기 때문에 매번 이런식으로 조작하는 것은 비효율적인 방법입니다. 보다 효율적인 방법은 PEB.NtGlobalFlag의 값을 0으로 변경하는 것입니다. Olly Advanced 플러그인이나 anti-anti 플러그인을 이용하면 디버깅을 시작할 때 이 값을 0으로 설정하여 탐지를 우회할 수 있습니다.  

맺음말
그 간 블로그에 올라온 글을 읽어보면 느끼겠지만 Anti Reversing 테크닉은 그다지 어려운 것들이 아닙니다. 관련된 지식의 난이도도 높지않고 구현하기도 어렵지 않은 것들이 대부분입니다. 하지만 리버서라면 반드시 알고 있어야 할 내용들이죠. 이 글이 조금이라도 도움이 되었으면 하는 바램입니다. 밤샜더니 졸립네요.. -.-;;

Posted by zesrever

개요
오늘은 Timing Detection이라고 불리는 간단한 안티 리버싱 테크닉에 대한 이야기입니다. 매우 상식적인 이야기라 그다지 어려울 것이 없는 내용입니다. ^^; 각설하고~ 바로 시작하죠.

Timing Detection
 타이밍 디텍션 테크닉의 기본 개념은 매우 단순합니다. 먼저 RDTSC등의 (Read Time Stamp Counter) 명령을 이용하여 시스템 부팅 후 실행시점까지 경과된 CPU 사이클을 읽어들인 후  특정지점에서 다시 RDTSC를 이용하여  사이클을  읽어들여 차이값을 계산합니다. 그 차이값이 미리 설정된(또는 일정범위 안에서의 난수) 값보다 크면 디버거가 존재하는 것으로 판단하는 것입니다. 보통 디버깅을 할 때 디버그 인터럽트를 처리하는 과정에서 약간의 CPU 싸이클이 더 소모되는 점과 브레이크 포인트등을 걸면서 분석해가는 점(당연히오랜 시간이 소요되겠죠)을 이용한 것입니다. 간단한 예제 코드를 살펴보도록 하겠습니다.

[예제코드 1] TimingDetection1.asm

   .586
      .model flat, stdcall
      option casemap :none   ; case sensitive

      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc

      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib
 .data
      MainCaption db "디버거  탐지", 0h, 0h
       TextDebuggerFound db "디버거가 탐지되었습니다.",0h,0h
       TextDebuggerNotFound db "디버거가 탐지되지 않았습니다.",0h,0h
 .code
 
start:
  RDTSC         ;RDTSC는 경과된 CPU 사이클을 읽어 edx:eax에
                ;저장합니다.
             
  xor ecx, ecx    
  xor ebx, ebx
  mov ecx, edx       ;ecx에 경과된 CPU 사이클의 상위 32bit 저장
  mov ebx, eax       ;ebx에 경과된 CPU 사이클의 하위 32bit 저장
  rdtsc              ;다시 경과된 사이클을 읽어냄
  
  cmp edx, ecx       ;최초 측정된 사이클과 두번째 측정된 사이클의
                     ;상위 32bit값을 비교합니다.
  ja @DebuggerFound  ;상위 32bit 값이 증가되었으면 Deubbger가 존재하는
                     ;것으로 판단합니다.
             
  sub eax, ebx      ;마찬가지로 하위32bit값을 조사합니다.
  cmp eax, 0fffh    ;그 값의 차가 지정한 값보다 크면 Debugger가 존재하는
                    ;것으로 판단합니다.
  jbe @DebuggerNotFound
 
 @DebuggerFound:
  PUSH 30h
  PUSH offset MainCaption
  PUSH offset TextDebuggerFound
  PUSH 0
  CALL MessageBox
  PUSH 0
  CALL ExitProcess
 
 @DebuggerNotFound:
  PUSH 30h
  PUSH offset MainCaption
  PUSH offset TextDebuggerNotFound
  PUSH 0
  CALL MessageBox
  PUSH 0
  CALL ExitProcess
 
end start

위 코드를 디버거 없이 정상적으로 실행한 결과입니다.

사용자 삽입 이미지

아래는 예제 프로그램을 디버거로 오픈한 후 테스트한 결과입니다. 먼저 예제 프로그램을 ollydbg로 오픈한 후 아래와 같이 00401002번지에 브레이크 포인트를 설정하였습니다.
 
사용자 삽입 이미지

그런 다음 F9를 한 번 눌러 브레이크 포인트가 걸리는 것을 확인하고 다시 F9를 눌러 실행을 계속해 보았습니다.
사용자 삽입 이미지

디버거가 탐지되었군요. 어찌보면 당연한 결과라고 할 수 있겠습니다. 하지만 코드를 읽어가면서 다들 눈치챘겠지만 예제 코드에는 몇 가지 문제점이 존재합니다.

먼저 두 RDTSC 사이에 존재하는 코드에 브레이크를 걸지 않으면 너무나 쉽게 우회된다는 것이죠. 이 문제는 두 가지 방법으로 해결할 수 있습니다. 첫번째 방법은 먼저 RDTSC를 이용하여 CPU 사이클 값을 읽어낸 다음 의도적으로 익셉션을 유발시키고 나서 익셉션 핸들러 안에서 다시 RDTSC를 이용하여 사이클 값을 읽어내는 방법입니다. 디버거가 존재한다면 익셉션이 발생했을 때 일차적으로 프로그램이 정지하게 된다는 점을 이용한 것입니다. 코드를 수정하여 다시 테스트해 보도록 하겠습니다.

[예제 프로그램 3]. TimingDetection2.asm

   .586
      .model flat, stdcall
      option casemap :none   ; case sensitive

      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc

      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib
 .data
      MainCaption db "디버거  탐지", 0h, 0h
       TextDebuggerFound db "디버거가 탐지되었습니다.",0h,0h
       TextDebuggerNotFound db "디버거가 탐지되지 않았습니다.",0h,0h
 .code
 
start:
 ASSUME FS:NOTHING
  PUSH  offset @ExceptionHandler
  PUSH FS:[0]
  MOV  FS:[0], ESP
 
  RDTSC         ;RDTSC는 경과된 CPU 사이클을 읽어 edx:eax에
                ;저장합니다.
 
  ADD [EAX], EAX ;익셉션 유발, 디버깅을 하는 경우 이 지점에서 디버기가
                 ;정지합니다. 여기에서 시간 차가 발생합니다.
 
 
  PUSH 0
  CALL ExitProcess
 
 @ExceptionHandler:
  PUSH EBP
  MOV  EBP, ESP
  PUSH ESI               ;callee saved register
                         ;ContextRecord를 가르키기 위해 ESI 사용
                        
  MOV  ESI, [EBP + 10h]  ;3번째 아규먼트(ContextRecord)의 값을 ESI에 저장
 
  RDTSC
    
  cmp edx, [ESI+0A8h] ; [ESI+0A8h]는 익셉션 유발 당시의 EDX 값
  ja @DebuggerFound 
            
  sub eax, [ESI+0B0h] ; [ESI+0B0h]는 익셉션 유발 당시의 EAX 값  
  cmp eax, 0ffffh   
 
  jbe @DebuggerNotFound
 
 @DebuggerFound:
  PUSH 30h
  PUSH offset MainCaption
  PUSH offset TextDebuggerFound
  PUSH 0
  CALL MessageBox
  JMP  @ExceptionHandler_end
 
 @DebuggerNotFound:
  PUSH 30h
  PUSH offset MainCaption
  PUSH offset TextDebuggerNotFound
  PUSH 0
  CALL MessageBox
 
 @ExceptionHandler_end :
  MOV  ECX, DWORD PTR DS:[ESI+0B8h] ;EIP값을 변경하기 위해 ECX에 저장
  ADD  ECX, 2                       ;ADD [EAX], EAX는 2bytes
  MOV  DWORD PTR DS:[ESI+0B8h], ECX
  XOR EAX, EAX                     
  POP ESI
  MOV ESP, EBP
  POP EBP
  RET
 
end start

아래는 테스트 결과입니다. Ollydbg로 오프한 다음 F9를 눌러 실행시키면 아래와 같이 익셉션이 발생하는 지점에서 일단 정지합니다. (이미 RDTSC가 실행된 후죠. 시간은 계속 가고 있습니다.)

사용자 삽입 이미지

Shift+F9를 눌러 익셉션을 처리하도록 하겠습니다.
사용자 삽입 이미지

위에서 보는 바와 같이 디버거가 정상적으로 탐지되었습니다.

두번째 방법은 RDTSC를 감추는 방법입니다. 꼼수입니다. ^^; 생각해 볼 수 있는 방법 중 하나는 기존의 DLL등에서 RDTSC를 호출하는 부분을 찾아 해당 주소로 점프하는 방법을 사용하는 것입니다. ollydbg에서 Ctrl+B를 눌러 ntdll.dll, kernel32.dll, user32.dll등 주요 DLL에서 "0f 31"을 찾아보았습니다. "0f 31"은 RDTSC의 opcode입니다. 몇 분정도 삽질한 결과 제가 사용하고 있는 비스타에서는 쓸만한 코드를 찾을 수 없었습니다. 지금 노트북에 있는 마땅한 vmware 이미지로는 Windows XP SP1밖에 없네요. 일단 Windows XP SP1에서는 다음의 주소에서 쓸만한 놈을 찾을 수 있었습니다. 아마 XP SP2에서도 찾을 수 있을 듯합니다. Windows 2000이나 Windows 2003은 찾아보질 않아서... ^^;

사용자 삽입 이미지

 77FA8797로 점프하면 되겠습니다. RDTSC로 읽어들인 클럭 수가 ECX+8번지와 ECX+C번지에 저장되므로 점프하기 전에 ECX값을 적절히 조절할 필요가 있겠습니다. 또한 RDTSC와 RETN 사이에 있는 각 커맨드로 인해 문제가 발생하지 않도록 하기 위해 몇 개의 코드를 삽입할 필요가 있습니다. 아래는 예제코드 1을 수정한 것입니다. 아래 코드는 개념 설명을 목적으로 한 것으로 실제 사용하기에는 좀 부적합 합니다. 유념하시고 살펴보시기 바랍니다.

[예제 코드 3] TimingDetect3.asm
start:
  mov esi, offset Garbage    ;77fa87a5번지에 있는 LOCK XADD [ESI],EAX
                             ;에 의해 익셉션이 발생하지 않도록 하러면
                             ;ESI가 유효한 주소값을 가져야 합니다.
 
  mov ecx, offset SavedEAX1 - 8

  push 01h           ;77fa87b6에있는 retn 4때문에 삽입했습니다.
                     ;즉 return 후 ESP = ESP + 4가 되므로
                     ;에러가 발생하는 것을 방지하기 위해 추가한 코드입니다.

  push offset @ReturnHere1   ;점프 후 복귀할 주소를 스택에 push합니다.
                             ;77fa87b6번지에 있는 retn 4가 실행되면
                             ;@ReturnHere1으로 복귀할 것입니다.
  push 02h                   ;77fa87b5번지에 POP ESI 때문에 삽입했습니다.
  push 77fa8797h
  ret
 @ReturnHere1:
  mov esi, offset Garbage
  mov ecx, offset SavedEAX2 - 8
  push 01h          
  push offset @ReturnHere2  
                 
               
  push 02h          
  push 77fa8797h
  ret
 
 @ReturnHere2:
  mov edx, [SavedEDX2]
  mov eax, [SavedEAX2]
  sub edx, [SavedEDX1]
  cmp edx, 01h          ; 이 값은 시스템마다 다를 수 있습니다.
                        ; 최적화된 값을 찾으려면 RDTSC 사이에 호출되는 각 명령어
                        ; 가 소비하는 싸이클 수를 다 계산해야 하지만
                        ; 단순 삽질로 찾아낸 값입니다. 따라서 부정확할 수 있습니다.
  ja  @DebuggerFound
  sub eax, [SavedEAX1]
  cmp eax, 0f0000000h   ; 역시 부정확한 값입니다.
  jbe @DebuggerNotFound


실제 테스트한 결과입니다.  

사용자 삽입 이미지

성공한 장면을 삽입하였습니다만... vmware(Windows XP SP1 이미지) 상에서 테스트해 본 결과 주석대로 오차가 많이 발생하여 딜레이 시간에 따라 탐지가 되었다가 안되었다가 하는군요.  개념만 파악 용도로 작성되었다는 점 이해해 주세요. ^^;

문제점이 어느 정도 보완되었나요... 사실 큰 문제점이 몇 가지 더 있습니다.
RDTSC를 이용하여 시간을 계산할 때 일반적으로 고려해야 할 점들인데요, 먼저 Counter Overflow 발생에 대한 고려를 해야 합니다. 또한 RDTSC와 RDTSC 사이의 명령어가 순차적으로 실행됨이 보증되지 않는다면 정확한 디버거 탐지가 힘들어진다는 점도 고려해야 합니다. 후자의 경우 CPUID라는 명령어를 이용하면 되는데... 여기에도 복잡한 문제가 존재합니다. 어쨌든 이 부분들은 다루려고 하는 주제에서 사~알짝 벗어나므로 생략하도록 하겠습니다. 생략한 부분에 대한 구체적인 내용은 Using the RDTSC Instruction for Performance Monitoring 을 참고하세요.


Timing Detection 우회 방법
참 재미있게도 간단한 기법임에도 개인적으로는 우회하기가 매우 매우 번거롭게 느껴집니다. 간단하게 구현되어 있는 경우 찾기도 쉽고 우회하는 것도 번거롭지 않겠지만 그런 순진한 코드를 본 적이 별로 없습니다.

가장 근본적인 우회 방법은 역시 커널 드라이버를 작성하는 것입니다. RDTSC가 실행될 때마다 이를 가로채어 리턴값을 조작하도록 하면 되겠죠. 매우 다행스럽게도 Olly Advanced 플러그인에는 유사한 개념의 커널 드라이버가 구현되어 있습니다.

Olly Advanced에서 사용하는 방법은  아래와 같습니다. (아래의 내용은 BlackHat 2007에서 발표된 Art of Unpacking에서 발췌한 내용을 보충하여 정리한 것입니다.)

* 4번 컨트롤 레지스터(CR4)의 TSD(Time Stamp Disable) bit의 값을 1로 설정한다.
   TSD bit가 1로 설정되면 Ring0 이외의 모드에서 RDTSC가 실행될 때마다 GP(Gerneral
   Protection) 익셉션이 발생하게 된다.
* GP 익셉션(보통은 GPF라고 합니다. General Protection Fault)이 발생하면 GPF 핸들러가
   동작한다. GPF의 익셉션 넘버는 0xD이므로 GFP 핸들러의 주소는 IDT 베이스 주소(시작주소)
   로부터 0xD * 4bytes만큼 떨어진 곳에 저장되어 있을 것이다.
 *Olly Advanced 플러그인은 바로 IDT에 등록되어 있는 GPF 핸들러를  hooking 하여
  GPF가 발생한 경우 RDTSC가 원인인지를 조사한다. RDTSC 실행이 원인이 되어
  GPF가 발생한 경우 리턴값을 조작하는 방법으로 timing check를 우회한다.

요지인 즉슨... IDT hooking을 하여 RDTSC가 실행될 때가 실제 경과 사이클이 아닌 조작된 값을 전송하여 디버기를 속인다는 것이죠.  
 
맺음말
지금까지 RDTSC를 이용한 timing detection 기법에 대해서 알아보았습니다. 사실 RDTSC외에도  GetTickCount()를 이용할 수도 있고 SharedUserData에 포함된 TickCountLow를 직접 읽어내는 방법도 존재합니다.(TickCountLow값은 Vista에서 사용하지 않는 것으로 보입니다만...) 공부하시는데 참고하세요. 또한 위에서 언급한 방법 중 RDTSC를 감추는 방법은 다양한 형태로 응용이 가능합니다. 알아두시면 좋을 듯. 오늘도 밤이 늦었습니다. 이 글을 읽으시는 분이 몇 분이나 계실지 모르겠지만... ^^; 즐핵~하세요. (이번글은 내용은 그다지 어렵지 않은데, 쓰기가 좀 어렵네요. 무슨 상황인지...)
Posted by zesrever
헐랭이님의 블로그에서 퍼온 글입니다.




Laser Espionage Microphone (how-to) - Click here for funny video clips


래전 부터 말로만 듣던 도청 방법인데 음성의 진동을 레이저로 전달받아 녹음/재생하는 도청 방법입니다. 듣기로는 이러한 도청을 방지하기 위해서 창문을 미세하게 진동시키는 보안 장비도 있다던데... 시간나면 꼭 한 번 만들어 봐야 겠습니다. 예전에 TEMPEST 실험했던 것 이후로 가장 재미있는 놀이가 될 것 같습니다. ^^;
Posted by zesrever

BLOG main image
Slow but Steady, Broad and Deep ... by zesrever

공지사항

카테고리

분류 전체보기 (44)
Digital Forensics (4)
Reverse Engineering (21)
Vulnerability (2)
Secure Coding (0)
Book Story (1)
Digital Life (7)
My Life (7)
세미나자료 (1)
개인용 (0)
Musics (0)
Total : 229,596
Today : 2 Yesterday : 255