Lumina server

 

Lumina 서버

IDA Pro 디컴파일러의 Lumina 서버 기능은 단순하지만 매우 강력한 기능입니다.

Hex Rays 사에서 관리하는 Lumina 서버에는 여러 알려진 함수들의 시그니처(함수 이름, 반환 타입, 인자 정보 등)가 저장되어 있고, 사용자는 자신이 알고 있는 시그니처를 공유하거나 모르는 함수에 대한 정보를 공유받을 수 있습니다. 즉, strip된 바이너리의 분석이 상당히 용이해지는 것입니다.

 

Hex Rays 사에서는 Lumina 서버와 관련된 기술을 공개한 적이 없고, 개인이 서버를 운영할 수 있는 기능 등을 제공하지 않습니다. 하지만 서버와의 통신 프로토콜을 분석하여 구축된 프리서버가 있는데, 바로 Lumen입니다. 오늘은 IDA Pro에서 이 Lumen 서버를 사용하는 방법과 짧은 사용 경험을 나누어 보겠습니다. 참고로 Lumen 서버의 개발자가 직접 블로그에 포스팅한 프로토콜 분석 write-up도 존재합니다.

Introducing Lumen | Name A.
abda.nl/posts/introducing-lumen/

 

Lumen 사용하기

Lumen은 Lumina 서버 연동 기능을 제공하는 IDA Pro 버전 7.2 이상이면 자유롭게 사용할 수 있습니다.

먼저 IDA Pro 설치 경로의 cfg/ida.cfg 파일을 편집기로 열고, 주석 처리되어 있는 "LUMINA_HOST", "LUMINA_PORT" 설정 부분을 찾아 2개 행을 다음과 같이 수정합니다.

LUMINA_HOST = "lumen.abda.nl"; // note: the semicolon is important!
LUMINA_PORT = 1234

 

이후 Lumen 서버에서 제공하는 인증서 파일 hexrays.crt를 내려받고, IDA Pro 설치 경로로 옮기면 사용 준비가 끝납니다. IDA Pro를 실행 중이면 재시작합시다.

 

Lumen을 사용한 분석

심볼이 strip된 바이너리를 분석할 때 Lumen이 도움을 줄 수 있습니다.

다음은 dreamhack.io 사이트의 워게임 rev-login 문제 바이너리의 일부입니다. check2 함수는 std::string 타입의 string__id, string__password를 인자로 받아 여러 연산을 수행하는데, 함수 내부에서 char v18[24] 배열이 많이 사용되는 것을 확인할 수 있습니다.

__int64 __fastcall check2(__int64 string__id, __int64 string__password)
{
  ...
  char v18[24]; // [rsp+30h] [rbp-30h] BYREF
  ...
  v19 = __readfsqword(0x28u);
  sub_2A5A(v18);
  v16 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(string__id);
  v17 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(string__password);
  for ( i = 0; i < v17; ++i )
  {
    v2 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                     string__password,
                     i);
    v3 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                     string__id,
                     i) ^ v2;
    v12 = v3 ^ *(_BYTE *)sub_2A76(&unk_2092C0, i);
    sub_2BA0(v18, &v12);
  }
  for ( j = 0; j < v17 - 1; ++j )
  {
    v4 = *(_BYTE *)sub_2A76(v18, j);
    if ( v4 < *(_BYTE *)sub_2A76(v18, j + 1) )
    {
      v5 = 0;
      goto LABEL_13;
    }
  }
  for ( k = 0; k < v17; ++k )
  {
    v6 = (_BYTE *)sub_2A76(v18, k);
    v7 = *(_BYTE *)sub_2A76(&unk_209260, (unsigned __int8)(k ^ ~*v6));
    v8 = (_BYTE *)sub_2A76(v18, k);
    *v8 += v7;
  }
  v9 = sub_2C1A(v18);
  v10 = sub_2BD2(v18);
  sub_2CE4(v10, v9);
  v5 = sub_2B0D(v18, &unk_2092A0);
LABEL_13:
  func(v18);
  return v5;
}

 

IDA Pro의 상단 메뉴 바에서 Lumina-Pull all metadata 옵션을 클릭하면 Lumen 서버에 존재하는 함수 시그니처에 대해 정보를 얻어올 수 있습니다. 

함수 시그니처를 받아오는 옵션

 

시그니처 정보를 얻어온 이후 좌측의 Functions window를 보면 새롭게 인식된 함수들은 녹색으로 표시됩니다. 함수명을 보면 std::vector, std::basic_string 관련 메소드 일부가 인식되었음을 확인할 수 있습니다.

초록초록한 함수들이 생겼다

 

이후 IDA Pro에서 디컴파일(F5)을 다시 하면, 위 코드의 LABEL_13 부분에서 func(v18) 코드가 std::vector의 소멸자를 호출하는 코드로 바뀐 것을 확인할 수 있습니다. 최초에 배열로 표시되었을 당시 변수 크기가 24바이트였음을 함께 고려할 때, 해당 변수는 std::vector 타입임을 짐작하는 데에 도움을 주고 있습니다. (std::vector는 멤버 변수로 3개의 포인터를 가집니다)

LABEL_13:
  std::__cxx1998::vector<double,std::allocator<double>>::~vector((std::vector<std::shared_ptr<sio::message>> *const)v19);
  return v5;
}

 

다만 아직 프리서버인 만큼 Lumen 서버에 존재하는 시그니처 정보가 매우 방대하지는 못합니다. 또한 일부 함수의 경우 관계없는 전혀 다른 함수로 인식하는 경우가 종종 있습니다. 하지만 보여드린 예시와 같이 "타입을 전혀 모르겠네"에서 "std::vector라는데 확인해 볼까" 정도의 변화는 분석할 때 생각보다 차이가 있으니, 상황에 따라 분석을 적절히 보조하는 선에서 사용하면 도움이 될 듯 합니다.

'Miscellaneous' 카테고리의 다른 글

VMware Workstation 가상머신 고정 IP 할당  (0) 2021.02.22
Zsh, Oh My Zsh 설치하기  (0) 2020.08.26