메모리 분석으로 OS X 바이너리 쉽게 언패킹하기

일전에 FIOS에서 "실전 메모리 분석"이라는 제목으로 발표한 적이 있다. 메모리 분석 자체를 어렵게 생각하는 사람도 많고 포렌식을 하더라도 한번도 접해보지 못한 사람들이 많다보니 1시간이라는 제한적인 시간 내에서 많은 내용을 다루기 쉽지 않았다(+이로인한 시간 지연은 덤..). 이러한 어려움에는 가능한 많은 내용을 세세하게 다루고자하는 필자의 성격도 한 몪했다. 🙂
이 발표에서 다뤘던 내용 중 "메모리 분석으로 패킹된 바이너리를 쉽게 언패킹하는 방법"이 있다. 코드가 실행되려면 결국 메모리에서 다양한 코드 보호와 압축해제 과정을 거친 날 것(Raw)의 코드가 맵핑되니 이 코드를 덤프하여 분석하면 된다는 내용이였다.

요번 글에서 다룰 내용은 OS X 메모리 분석으로 패킹된 OS X 바이너리의 언패킹 코드를 덤프하는 내용이다. 사실 이 원리는 윈도의 그것과 같다. 단지 그냥 했던 내용을 기록하기 위한 차원에서 작성하였다. 몇 주전에 OS X 바이너리 리버서인 @osxreverser의 OS X 보안 제품을 만드는 @synack가 글을 인용하여 블로그 포스팅을 한적 있다 [1].

이 글을 보면, 해킹팀에서 배포된 바이너리인 install(SHA256:58e4e4853c6cfbb43afd49e5238046596ee5b78eca439c7d76bd95a34115a273)은 애플 보호된 바이너리(Apple Protected Binary) 형태로 유포되었으며, 애플의 바이너리 보호(암호화)를 해제(복호화)하더라도 해킹팀 자체 팩커를 통해 팩킹된 바이너리를 만나게 된다. 이 패커는 lldb로 덤프하거나, @osxreverser가 개발한 readmem으로 덤프해서 분석할 수 있다는 내용이 작성되어 있다. 이러한 내용은 악성코드 샘플을 추출하여 분석할 때는 유용하게 사용할 수 있지만, 악성코드로 이미 침해를 당한 시스템의 경우에는 언제 휘발성 데이터가 손실될지 모르는 상황에서 라이브에서 터미널 명령어를 치면서 분석하는게 여러모로 부담스러운게 사실이다. 침해사고에서는 메모리 분석으로 이러한 문제점을 해결할 수 있다.

해킹팀 바이너리에서 우리가 볼 부분은?

해킹팀 바이너리를 상세 분석하는건 별도의 글로 작성 중이다.

앞서 설명한 바와 같이 해킹팀 바이너리에는 2가지의 분석 방해 기법이 적용되어 있다. 하나는 "Apple Protected Binary"이고, 다른 하나는 "keypress"라고 불리는 해킹팀에서 인-하우스로 개발된 패커이다. 배포된 바이너리를 보면 하나의 바이너리에 두가지 보호 기법이 적용되어 있는 것은 아니다 (물론 할 수도 있지만..). 배포된 바이너리는 APB 상태로 일반 사용자 권한으로 실행되면 다음과 같은 상태가 된다 [2].

  • 바이너리 파일 – 경로 : ~/Library/Preferences/8pHbqThW/_9g4cBub.psr
  • 바이너리 파일 – 경로 : ~/Library/Scripting Additions/FinderEvent/Contents/MacOS/w1_X-Hye.gn6
  • 자동 실행 정보 – 경로 : ~/Library/LaunchAgents/com.apple.FinderExtAvt.plist

3 개의 파일 중 자동 실행으로 실행되는 "_9g4cBub.psr" 파일의 메타 정보를 분석하면, 다음과 같다.

해당 바이너리는 LC_UNIXTHREAD 커맨드의 EIP를 엔트리 포인트로 구동된다. 또한 세그먼트 커맨드의 권한(initprot)를 보면 READ|WRITE|EXECUTE 권한(0x07)을 가지고 있다. IDA로 실제 코드를 보면 대부분이 데이터 영역으로 분류되어 있으며, 짧은 코드 영역에서는 의미가 불분명한 내용이 기술되어 있다. 이 파일이 해킹팀의 커스텀 패커로 압축된 바이너리이다.

해킹팀의 OS X 바이너리 패킹 방식 이해

사실 메모리 분석을 통한 언패킹은 모든 상황에서 통용되진 않는다. 그 이유를 파악하기 위해 언패킹이 가능한 해킹팀의 패커(keypress)에 대해 알아보고자 한다. 사실 해킹팀이 사용하는 패커는 MPRESS라는 패킹 알고리즘으로 알려진 알고리즘이며, 이 바이너리의 언패킹 절차는 @osxreverser가 잘 기술해두었으므로 여기에서는 간단한 내용만 알아보겠다. 자세한 내용은 해당 자료를 참조하면 된다 [3].
MPRESS는 크게 하나의 패킹된 데이터 영역과 두군데의 스텁 코드로 구성된다. 각 스텁 코드의 역할은 다음과 같다.

  • 첫 번째 스텁코드 : 패킹된 데이터 영역을 언팩하여 특정 주소에 작성한다. 두 번째 스텁코드를 디코딩하고 제어를 넘긴다.
  • 두 번째 스텁코드 : 각 세그먼트의 권한 정보를 정상적인 상태로 수정한다. 코드가 정상적으로 실행되도록 스택을 정리한다.

위에서 잠시 언급한 "LC_UNIXTHREAD"의 EIP는 첫 번째 스텁코드(0x1803120)를 가리키고 있다. 이 스텁 코드가 실행되면 패킹된 바이너리 전체를 0x1000 주소에 언팩하고 두 번째 스텁코드를 디코딩한다.

두 번째 스텁코드는 0x1000 주소의 언팩 코드를 실행하기 전에 언팩한 바이너리의 정상적인 실행을 위한 환경을 구성한다. 우선 페이지 전체가 RWX 형태로 할당되어 있을 것이므로, 각 섹션에 맞게 메모리 페이지의 권한 정보를 복구한다. 또한 동적 링커인 dyld를 매핑하고, 스택과 환경변수를 정리한다. 동적 링커가 실행되면 메모리에 맵핑된 원본 바이너리의 함수 정보를 링킹하고 언팩 코드의 엔트리 포인트로 제어를 이전하게 된다.

언패킹 하기

덤프한 바이너리를 언패킹해보자. 해킹 팀의 객체를 가상머신 환경에서 실행한 후, 가상머신을 Suspended 상태로 돌리고 메모리 이미지를 분석하였다. 우선 프로세스 정보를 확인하면 다음과 같다.

프로세스 중, PID 829 프로세스가 악성 프로세스로, 로드된 경로를 확인하면 다음과 같다.

악성코드는 "/Users/victim/Library/Preferences/8pHbqThW" 에서 동작 중이다. 해당 바이너리를 메모리 분석 도구의 프로세스 덤프 플러그인(machdump)으로 덤프하였다.

해당 바이너리의 커맨드(메타) 정보를 확인하면, 바이너리만 확인했을 때와 다른 구조를 가지고 있는 것을 확인할 수 있다. 일단 파일 상태의 헤더 정보를 확인하면 커맨드 갯수(ncmds)가 3인걸 확인할 수 있다.

메모리에서 덤프한 바이너리는 커맨드 갯수가 36개로 늘어난 것을 확인할 수 있다.

이제 디스어셈블러로 분석하면 된다! 😀 단, 메모리에 매핑된 상태에서 코드를 덤프한 것이므로 바이너리 외부에 있는 함수를 호출하는 경우 제대로된 함수 매핑이 불가능하다. 이는 메모리 분석 도구에서 함수 테이블을 추출해줘야 해결 가능한 것임.

레퍼런스

  1. HackingTeam Reborn; A Brief Analysis of an RCS Implant Installer, https://objective-see.com/blog/blog_0x0D.html.
  2. 루트 권한이면 다른 행동을 하는 것으로 알고있다.
  3. https://www.syscan360.org/slides/2014_EN_FuckYouHackingTeamFromPortugalWithLove_PedroVilaca.pdf