개요
분석을 하다 보면 COM Object 를 생성하는 함수인 CoCreateInstance()
란 API를 많이 접하게 됩니다. MSDN 은 이 함수를 “특정 CLSID1 를 통해 이와 연관된 초기화되지 않은 오브젝트를 생성하는 함수” 라고 설명합니다. 좀 풀어서 설명하자면, OS 와 기설치된 모듈에 구에받지 않고 어디에서든 동일한 기능을 쓸 수 있도록 지원하는 함수인데요, 쓰고자 하는 기능을 CLSID 를 통해 가져올 수 있습니다.
CoCreateInstance 원형
HRESULT CoCreateInstance(
_In_ REFCLSID rclsid,
_In_ LPUNKNOWN pUnkOuter,
_In_ DWORD dwClsContext,
_In_ REFIID riid,
_Out_ LPVOID *ppv
);
사용법과 각 변수의 의미에 관한 자세한 설명은 도모네 블로그 를 참고하세요.
이 API는 샘플 분석 시 매우 자주 등장하는데요, 긴 얘기 짧게 하자면 CoCreateInstance()
로 인터페이스를 갖다 쓰면 해당 인터페이스가 무슨 기능을 하는지 주어진 코드만으론 알 길이 막막하기에 분석에 어렵다는 것입니다. 최근 이슈화된 Mirai for Windows 샘플의 예입니다.
CoCreateInstance(rclsid, pUnkOuter, dwClsContext, &riid, &ppv);
가 호출되면, riid 에 해당하는 인스턴스를 ppv에 가져옵니다. 이어서 **ppv()
혹은 *(*ppv+8)()
로 함수를 호출하는 것을 볼 수 있습니다. ppv 가 무슨 역할을 하는지는 해당 함수 안으로 들어가봐야 하지만, 이는 지역변수이기에 그럴 수 없습니다.
그러면 어떻게 알 수 있을까요?
GUID 확보
원하는 컴포넌트와 인터페이스를 갖고 오기 위해선 이들을 식별할 수 있어야 합니다. 그리고 그 식별자는 GUID(GUID 와 CLSID 는 동일한 구조체입니다) 의 형태를 띕니다. 위 경우 ppv 가 뭔지 알려면 riid를 확인하면 되겠군요. IDA 에서 riid 를 더블클릭해 들어가겠습니다.
대게 이 GUID 는 데이터섹션에 저장돼있습니다. DWORD 0, WORD 0, WORD 0, BYTE[8] C0,00,00,00,00,00,00,46 이군요. 한눈에 안 들어옵니다. 이 값이 저장된 메모리 0x0054AB60 를 좀 편하게 보도록 하죠.
이제 쉽게 ctrl+c 로 쉽게 복사할 수 있습니다. 이제 GUID 를 확보했으니 어떤 인터페이스를 갖다 쓰는지 검색만 하면 됩니다.
00 00 00 00 00 00 00 00 C0 00 00 00 00 00 00 46
GUID 검색
위에서 GUID 를 확보했습니다. GUID 로 MSDN 을 검색하면 어떤 인터페이스가 있는지, 어떤 메쏘드를 제공하는지 알 수 있습니다. 00 00 00 00 00 00 00 00 C0 00 00 00 00 00 00 46 를 구글링 한 결과입니다.
뭔가 이상한 결과가 나왔습니다. 아무래도 띄어쓰기 때문인 것 같습니다. GUID 구조체 원형에 맞는 형식으로 바꿔 검색하겠습니다.
GUID 구조체 원형
typedef struct _GUID {
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[8];
} GUID;
00000000 0000 0000 C000000000000046
IUnknown 인터페이스가 정상적으로 검색되는군요. 여기서 주의할 점은 GUID 구조체는 용도에 따라 여러 다른 이름으로 불립니다. CLSID, LIBID, CATID, AppID, MID, IPID 등등… 하지만 모두 동일한 GUID 구조체를 사용한다는 점이 중요합니다.
이제 IDA에서 지역변수 ppv 의 변수명과 자료형을 IUnknown 과 LPUNKNOWN 으로 바꿔주면 어떻게 되는지 확인해보시죠.
훨씬 보기 쉽게 변했습니다. 한 가지 예만 더 들어보도록 하겠습니다.
Byte ordering
위에서 예를 들은 Mirai for windows 샘플의 다른 호출부분입니다. 앞서 했던 데로 stru_5D5A80 의 값을 검색해보겠습니다.
87A612DC 7F73 CF11 884D00AA004B2E24
분명 다른게 없는데 검색이 되질 않습니다. 뭐가 잘못됐을까요? 이유는 byte ordering 에 있습니다. 정수 자료형과 바이트 배열이 함께 있는 GUID 는 little endian 방식으로 기록되기에 이 값을 긁어 바로 검색 하면 원하는 결과를 얻을 수 없습니다. 아까의 경우엔 공교롭게도 정수 부분이 모두 0이기에 이 문제가 발생하지 않았던거죠.
따라서 아래와 같이 순서를 변경해 다시 검색해보겠습니다.
DC12A687 737F 11CF 884D00AA004B2E24
검색이 되지 않는 경우엔 CLSID 형식으로 바꿔서 검색해보세요. 동일한 구조체이지만, Data4[8]
의 에서 0번, 1번 인덱스를 띄어 써줍니다.
DC12A687 737F 11CF 884D 00AA004B2E24
정상적으로 검색이 됩니다. 이제 IDA에서 어떻게 보이는지 확인해볼까요
훨씬 보기 쉽게 바뀌었습니다. Hexray 가 보여주는 내용이 그리 정확도하지 않으므로, 굳이 자료형을 재정의 해주시는 것 보단, 오프셋으로 함수를 유추해 분석하는것이 빠르게 보실 땐 더 나을 것 같습니다.
이거 언제 손으로 바꾸고 있나 해서 파이썬으로 만들었습니다. 리버싱 관점에서 CoCreateInstance()
에 간략히 설명한 좋은 포스트가 있으니 한번 보셔도 좋을 것입니다.
- A CLSID is a globally unique identifier that identifies a COM class object. If your server or container allows linking to its embedded objects, you need to register a CLSID for each supported class of objects.CLSID MSDN ↩