IDA Pro용 ida_medigate C++ 플러그인
[목차]
컴파일된 C++ 코드의 리버스 엔지니어링은 재미가 없습니다. 컴파일된 C++ 코드의 정적 리버스 엔지니어링은 실망스럽습니다. 이를 어렵게 만드는 주된 이유는 가상 기능입니다. 컴파일된 C 코드와 달리 명확한 코드 흐름이 없습니다. 컴파일된 C 코드에서와 같이 함수를 보는 것보다 다음 가상 함수가 호출되는 것이 무엇인지 이해하려고 노력하는 데 많은 시간을 소비할 수 있습니다.
가상 기능을 조사할 때 외부 참조를 찾기 위해 노력해야 하는 시간은 의미가 없습니다.
너무 많은 C++ RE 프로젝트 후에 저는 이러한 유형의 연구를 위해 유연하고(Python) 안정적인(쉽게 유지 관리할 수 있는) 도구가 필요하다고 포기하고 결정했습니다. 이 플러그인의 대부분은 2018년 1월에 작성되었으며 최근에는 먼지를 제거하고 새로운 IDA(7.2) 클래스 지원에 대한 지원을 추가하기로 결정했습니다.
이 플러그인은 항상 "즉시" 작동하도록 고안된 것이 아니라 리버서를 위한 또 다른 도구로 사용됩니다.
플러그인은 두 부분으로 구성됩니다.
이 첫 번째 부분은 두 번째 부분에 종속되지 않으므로 플러그인의 API를 기반으로 해당 클래스를 수동으로 정의함으로써 플러그인을 사용하여 RTTI가 포함되지 않은 바이너리를 리버스 엔지니어링할 수 있습니다.
플러그인을 독특하게 만드는 것은 연구원이 이미 익숙한 동일한 환경을 사용하고 새로운 메뉴나 개체를 추가하지 않으며 알려진 IDA 빌딩 블록(구조, 공용체, 구조 멤버 유형 등)을 기반으로 한다는 사실입니다. ) - 이를 통해 플러그인은 IDA가 지원하는 모든 아키텍처에 대해 C++ 추상화를 지원할 수 있습니다 .
참고: RTTI 파서는 x86/x64 g++ RTTI를 구문 분석하지만 해당 구조를 통해 더 많은 아키텍처 및 컴파일러에 대한 지원을 쉽게 추가할 수 있습니다.
plugins/
디렉토리 아래). medigate_cpp_plugin
plugins
디렉터리에 복사하고 idapythonrc.py
파일에 소스 코드 경로를 추가합니다.
plugins/ida-referee/referee.py
동일한 디렉터리에 복사합니다.
바이너리 원본 소스 코드가 다음과 같다고 가정합니다( examples/a.cpp
).
using namespace std ;
class A {
public:
int x_a;
virtual int f_a ()=0;
};
class B : public A {
public:
int x_b;
int f_a (){x_a = 0 ;}
virtual int f_b (){ this -> f_a ();}
};
class Z {
public:
virtual int f_z1 (){cout << " f_z1 " ;}
virtual int f_z2 (){cout << " f_z2 " ;}
};
class C : public B , public Z {
public:
int f_a (){x_a = 5 ;}
int x_c;
int f_c (){x_c = 0 ;}
virtual int f_z1 (){cout << " f_z3 " ;}
};
int main ()
{
C *c = new C ();
c-> f_a ();
c-> f_b ();
c-> f_z1 ();
c-> f_z2 ();
return 0 ;
}
바이너리는 제거되었지만 RTTI를 포함합니다.
바이너리를 로드하면 main
함수(32비트 버전의 sub_84D
)는 다음과 같습니다.
다음을 사용하여 g++ RTTI 파서를 시작하고 실행합니다.
from ida_medigate.rtti_parser import GccRTTIParser
GccRTTIParser.init_parser()
GccRTTIParser.build_all()
이제 구조체 C를 새로 고치고(설명 섹션 참조) v0
C *
로 캐스팅하고 다시 디컴파일합니다.
RTTI가 없는 경우에도 우리 인프라에서는 여전히 C++ 클래스를 수동으로 정의할 수 있습니다. 동일한 예(examples/a32_stripped)의 경우 구조체 B를 수동으로 생성한 다음 가상 테이블을 선택하고 다음을 입력할 수 있습니다.
from ida_medigate import cpp_utils
cpp_utils.make_vtable("B")
make_vtable
선택한 영역 대신 vtable_ea
및 vtable_ea_stop
가져올 수도 있습니다.
그런 다음 구조체 C를 만들고 상속을 적용합니다.
cpp_utils.add_baseclass("C", "B")
이제 클래스 C vtable을 선택하고 다음을 입력하여 다시 작성할 수 있습니다.
cpp_utils.make_vtable("C")
구조 Z를 추가하고 vtable도 다시 빌드하면 이제 멋진 부분이 됩니다.
cpp_utils.add_baseclass("C", "Z", 0x0c, to_update=True)
오프셋 0x0c에서 Z의 C 상속을 적용하고 구조체도 새로 고칩니다(설명 참조).
마지막으로 남은 것은 Z의 인터페이스를 구현하는 C의 두 번째 vtable을 업데이트하는 것입니다. 이 vtable을 표시하고 다음을 입력합니다.
cpp_utils.make_vtable("C", offset_in_class=0x0c)
ida_medigate는 이 vtable이 클래스 Z의 vtable이고 결과는 다음과 같다는 것을 알고 있습니다.
최종 결과는 RTTI 사례와 동일합니다.
새로운 vtable 구조체가 생성되면(사용자가 수동으로 RTTI 파서에 의해) 아직 변경되지 않은 각 함수의 이름이 바뀌고 디컴파일된 후 첫 번째 인수가 this
로 설정됩니다.
해당 함수에 해당하는 구조의 멤버를 두 번 클릭하면 해당 함수로 이동하므로 사용자는 편리한 방법으로 C++ 코드 흐름을 읽을 수 있습니다.
vtables의 함수 또는 해당 함수 포인터 멤버의 모든 이름이나 유형 변경은 모두 연결되고 동기화됩니다. 이는 예를 들어 사용자가 디컴파일러 창을 통해 vtable 멤버 유형을 변경할 수 있으며 이 새로운 유형(프로토타입)이 대상 함수에도 적용된다는 것을 의미합니다.
이전 이미지의 15번째 줄에는 B::sub_9A8(소스 코드의 B::f_b)에 대한 호출이 있습니다. 이 함수 인수는 B *
입니다.
그러나 이 함수는 C
인스턴스(업캐스팅)에 의해 호출될 수도 있습니다. 우리는 인스턴스가 호출할 가상 함수를 보고 싶습니다. 잠재적인 파생 클래스가 많이 있다고 가정하므로 this
C *
로 캐스팅하는 것이 항상 가능하지는 않습니다. 이러한 이유로 우리는 서로 다른 가상 테이블을 가진 아들이 있는 각 기본 클래스에 대해 공용체를 구현합니다. Alt+y(다른 공용체 멤버를 선택하는 단축키)를 클릭하여 B
파생 상품의 다른 파생 가상 테이블을 표시하도록 선택할 수 있습니다.
따라서 궁극적으로 우리는 다른 가상 함수에 대한 특정 호출만 "캐스트"할 수 있습니다.
좌절한 C++ 리버스 엔지니어의 성배. 우리는 가상 함수에서 이를 나타내는 vtable 구조체의 멤버까지 외부 참조를 유지합니다!
이것을 ida-referee
와 결합하면 가상 함수 호출의 모든 외부 참조를 추적할 수 있습니다!
제한 사항: 이미 디컴파일된 가상 호출만 추적할 수 있습니다. 다행스럽게도 자동 분석은 함수 사이에 인수 유형을 채우는 방법을 알고 있으므로 더 많은 인수 캐스팅 -> 모든 관련 함수 디컴파일 -> 코드를 다시 읽고 더 많은 인수 캐스팅(...)의 반복 프로세스를 통해 이 능력은 실제로 강한!
IDAPython에서 구조 멤버를 하위 클래스로 표시하는 방식은 IDB에 즉시 동기화되지 않습니다. 우리가 하는 해킹은 동기화가 실행되도록 구조를 편집하는 것입니다. 당신은 또한 사용할 수 있습니다
utils.refresh_struct(struct_ptr)
구조체 끝에 더미 필드를 추가한 다음 정의를 해제합니다.