들어가면서
제스리버입니다. 제가 강의를 좀 많이 하는 편인데요, 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 같은 툴 사용하시면 됩니다.

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


그럼 시작합니다.
 


크리에이티브 커먼즈 라이선스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by zesrever

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





크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by zesrever
^^; 이런 서비스가 있다고 해서 함 해봤는데...

사용자 삽입 이미지
 

음냐.. 6만9천원가지고 뭘하쥐? 도메인 기간 연장 ㅋ
크리에이티브 커먼즈 라이선스
Creative Commons License
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같은 스크립트를 활용합니다.

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

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


크리에이티브 커먼즈 라이선스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
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 문제를 풀어볼까요..

크리에이티브 커먼즈 라이선스
Creative Commons License
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]과 동일한 결과를 얻을 수 있습니다. 그럼 문제를 계속 분석해 보도록 하겠습니다.

다음글로 이어집니다.

크리에이티브 커먼즈 라이선스
Creative Commons License
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로 가보실까요..



크리에이티브 커먼즈 라이선스
Creative Commons License
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을 수행하는 것입니다. 일단 컴파일 한 다음 디스어셈블러와