Ломаем хаКс полностью. Читаем машинные коды как открытую книгу

в 5:56, , рубрики: c++, haxe, ida, OpenFL, дизассемблер, разработка игр, реверс-инжиниринг

Ломаем хаКс полностью. Читаем машинные коды как открытую книгу - 1Если haXe оттранслирован в C++, а из него — в машинные коды, это может показаться безнадёжным, тем более, на первый взгляд этот код пестрит вызовами виртуальных методов, которые, не запуская отладчик, трудно соотнести с адресами тел методов.

Но всё не так уж плохо. Даже при отключенной поддержке сценариев (HXCPP_SCRIPTABLE) в файле можно обнаружить строки с названиями методов и полей. Разбираем, как можно размотать этот клубок, сопоставить имена методов с их адресами и смещениями в таблице виртуальных методов.

Немного теории

После трансляции в C++ все классы haXe наследуются от hx.Object (определён в hxcpp/include/hx/Object.h). Особый интерес представляют следующие методы:

   virtual Dynamic __Field(const String &inString, hx::PropertyAccess inCallProp);
   virtual Dynamic __SetField(const String &inField,const Dynamic &inValue, hx::PropertyAccess inCallProp);

Эти методы переопределяются в классах, оттранслированных в C++, и реализация их везде выглядит примерно так:

src/openfl/geom/Matrix.cpp

Dynamic Matrix_obj::__Field(const ::String &inName,hx::PropertyAccess inCallProp)
{
	switch(inName.length) {
	case 1:
		if (HX_FIELD_EQ(inName,"a") ) { return a; }
		if (HX_FIELD_EQ(inName,"b") ) { return b; }
		if (HX_FIELD_EQ(inName,"c") ) { return c; }
		if (HX_FIELD_EQ(inName,"d") ) { return d; }
		break;
	case 2:
		if (HX_FIELD_EQ(inName,"tx") ) { return tx; }
		if (HX_FIELD_EQ(inName,"ty") ) { return ty; }
		break;
	case 5:
		if (HX_FIELD_EQ(inName,"clone") ) { return clone_dyn(); }
		if (HX_FIELD_EQ(inName,"scale") ) { return scale_dyn(); }
		if (HX_FIELD_EQ(inName,"setTo") ) { return setTo_dyn(); }
		break;
	case 6:
		if (HX_FIELD_EQ(inName,"concat") ) { return concat_dyn(); }
		if (HX_FIELD_EQ(inName,"equals") ) { return equals_dyn(); }
		if (HX_FIELD_EQ(inName,"invert") ) { return invert_dyn(); }
		if (HX_FIELD_EQ(inName,"rotate") ) { return rotate_dyn(); }
		break;
	case 7:
		if (HX_FIELD_EQ(inName,"__array") ) { return __array; }
		if (HX_FIELD_EQ(inName,"toArray") ) { return toArray_dyn(); }
		break;
	case 8:
		if (HX_FIELD_EQ(inName,"copyFrom") ) { return copyFrom_dyn(); }
		if (HX_FIELD_EQ(inName,"identity") ) { return identity_dyn(); }
		if (HX_FIELD_EQ(inName,"toString") ) { return toString_dyn(); }
		break;
	case 9:
		if (HX_FIELD_EQ(inName,"copyRowTo") ) { return copyRowTo_dyn(); }
		if (HX_FIELD_EQ(inName,"createBox") ) { return createBox_dyn(); }
		if (HX_FIELD_EQ(inName,"translate") ) { return translate_dyn(); }
		break;
	case 10:
		if (HX_FIELD_EQ(inName,"to3DString") ) { return to3DString_dyn(); }
		break;
	case 11:
		if (HX_FIELD_EQ(inName,"copyRowFrom") ) { return copyRowFrom_dyn(); }
		if (HX_FIELD_EQ(inName,"setRotation") ) { return setRotation_dyn(); }
		if (HX_FIELD_EQ(inName,"toMozString") ) { return toMozString_dyn(); }
		if (HX_FIELD_EQ(inName,"__toMatrix3") ) { return __toMatrix3_dyn(); }
		break;
	case 12:
		if (HX_FIELD_EQ(inName,"copyColumnTo") ) { return copyColumnTo_dyn(); }
		if (HX_FIELD_EQ(inName,"__transformX") ) { return __transformX_dyn(); }
		if (HX_FIELD_EQ(inName,"__transformY") ) { return __transformY_dyn(); }
		break;
	case 13:
		if (HX_FIELD_EQ(inName,"__cleanValues") ) { return __cleanValues_dyn(); }
		break;
	case 14:
		if (HX_FIELD_EQ(inName,"copyColumnFrom") ) { return copyColumnFrom_dyn(); }
		if (HX_FIELD_EQ(inName,"transformPoint") ) { return transformPoint_dyn(); }
		break;
	case 16:
		if (HX_FIELD_EQ(inName,"__transformPoint") ) { return __transformPoint_dyn(); }
		break;
	case 17:
		if (HX_FIELD_EQ(inName,"createGradientBox") ) { return createGradientBox_dyn(); }
		break;
	case 19:
		if (HX_FIELD_EQ(inName,"deltaTransformPoint") ) { return deltaTransformPoint_dyn(); }
		if (HX_FIELD_EQ(inName,"__transformInverseX") ) { return __transformInverseX_dyn(); }
		if (HX_FIELD_EQ(inName,"__transformInverseY") ) { return __transformInverseY_dyn(); }
		break;
	case 22:
		if (HX_FIELD_EQ(inName,"__translateTransformed") ) { return __translateTransformed_dyn(); }
		break;
	case 23:
		if (HX_FIELD_EQ(inName,"__transformInversePoint") ) { return __transformInversePoint_dyn(); }
	}
	return super::__Field(inName,inCallProp);
}

Как можно видеть, полями в понимании транслятора хаКс оказывается не только то, что обычно считается полями, но и методы тоже, правда, в динамических обёртках, из которых их ещё предстоит вытащить.

Подготовка

Соответственно, начинать стоит с того, чтобы найти метод __Field. Например, в него можно попасть по обратной ссылке на строку с именем метода. Если почитать, какие в файле есть строки, то по обратным ссылкам можно попасть, например, в __ToString или RTTI. Из них по обратной ссылке надо перейти в VMT. Если строка является именем поля, то вместо __Field можно попасть в похожий метод __SetField, который хуже подходит, так как там нет ссылок на динамические обёртки для методов. Находясь в VMT, открываем переопределённые методы (выделяются по адресам) и ищем, какие из них похожи на __Field (видно большой switch в начале):

Начало __Field

.text:010B3DB8 var_30          = -0x30
.text:010B3DB8 var_2C          = -0x2C
.text:010B3DB8 var_28          = -0x28
.text:010B3DB8 var_20          = -0x20
.text:010B3DB8
.text:010B3DB8                 PUSH.W          {R4-R9,LR}
.text:010B3DBC                 SUB             SP, SP, #0x14
.text:010B3DBE                 MOV             R7, R2
.text:010B3DC0                 MOV             R4, R0
.text:010B3DC2                 LDR             R0, [R7]
.text:010B3DC4                 MOV             R9, R3
.text:010B3DC6                 MOV             R5, R1
.text:010B3DC8                 SUBS            R0, #4  ; switch 28 cases
.text:010B3DCA                 CMP             R0, #0x1B
.text:010B3DCC                 BHI.W           def_10B3DD0 ; jumptable 010B3DD0 default case
.text:010B3DD0                 TBH.W           [PC,R0,LSL#1] ; switch jump
.text:010B3DD0 ; ---------------------------------------------------------------------------
.text:010B3DD4 jpt_10B3DD0     DCW 0x1C                ; jump table for switch statement
.text:010B3DD6                 DCW 0x35

Начало __SetField

.text:010B48DC var_38          = -0x38
.text:010B48DC var_30          = -0x30
.text:010B48DC var_28          = -0x28
.text:010B48DC var_24          = -0x24
.text:010B48DC var_20          = -0x20
.text:010B48DC arg_0           =  0
.text:010B48DC
.text:010B48DC                 PUSH.W          {R4-R9,LR}
.text:010B48E0                 SUB             SP, SP, #0x1C
.text:010B48E2                 MOV             R7, R2
.text:010B48E4                 MOV             R8, R0
.text:010B48E6                 LDR             R0, [R7]
.text:010B48E8                 MOV             R6, R3
.text:010B48EA                 LDR             R5, [SP,#0x38+arg_0]
.text:010B48EC                 MOV             R9, R1
.text:010B48EE                 SUBS            R0, #6  ; switch 13 cases
.text:010B48F0                 CMP             R0, #0xC
.text:010B48F2                 BHI.W           def_10B48F6 ; jumptable 010B48F6 default case
.text:010B48F6                 TBH.W           [PC,R0,LSL#1] ; switch jump
.text:010B48F6 ; ---------------------------------------------------------------------------
.text:010B48FA jpt_10B48F6     DCW 0xD                 ; DATA XREF: .text:01329970↓o
.text:010B48FA                                         ; jump table for switch statement
.text:010B48FC                 DCW 0x25

__Field в таблице виртуальных методов идёт раньше, чем __SetField, и вариантов там обычно меньше. В данном примере 13 против 28.

Первый этап: ищем динамические обёртки

Когда найдены оба метода, нужно зайти в __Field, смотреть, куда идёт ветвление после 0 == memcmp и давать имена обёрткам. Попадаться при этом могут как обычные поля, так и обёртки. Научиться отличать их несложно, вот пример сначала обычного поля, потом динамической обёртки для метода:

.text:010B44B0 loc_10B44B0                             ; CODE XREF: __Field+16A↑j
.text:010B44B0                 LDR             R0, [R5,#0x20]
.text:010B44B2                 B               loc_10B4582
.text:010B44B4 ; ---------------------------------------------------------------------------
.text:010B44B4
.text:010B44B4 loc_10B44B4                             ; CODE XREF: __Field+1B0↑j
.text:010B44B4                 LDR             R2, =(get_error_dyn+1 - 0x10B44BA)
.text:010B44B6                 ADD             R2, PC ; get_error_dyn
.text:010B44B8                 B               loc_10B44D2

Встречалась, но не в этом файле, такая проблема, что указатели на обёртки не распознаются. Выглядит это как аномально большой целочисленный операнд оранжевого цвета. Через Ctrl+R его надо в IDA сделать смещением.

Второй этап: самые простые случаи

Для начала посмотрим, как вообще после трансляции в C++ расположены методы и обёртки для них:

src/openfl/geom/Matrix.cpp
// …
::lime::math::Matrix3 Matrix_obj::__toMatrix3( ){
	HX_STACK_FRAME("openfl.geom.Matrix","__toMatrix3",0xaf6ed17e,"openfl.geom.Matrix.__toMatrix3","openfl/geom/Matrix.hx",480,0xa0d54189)
	HX_STACK_THIS(this)
	HX_STACK_LINE(482)
	Float tmp = this->a;		HX_STACK_VAR(tmp,"tmp");
	HX_STACK_LINE(482)
	Float tmp1 = this->b;		HX_STACK_VAR(tmp1,"tmp1");
	HX_STACK_LINE(482)
	Float tmp2 = this->c;		HX_STACK_VAR(tmp2,"tmp2");
	HX_STACK_LINE(482)
	Float tmp3 = this->d;		HX_STACK_VAR(tmp3,"tmp3");
	HX_STACK_LINE(482)
	Float tmp4 = this->tx;		HX_STACK_VAR(tmp4,"tmp4");
	HX_STACK_LINE(482)
	Float tmp5 = this->ty;		HX_STACK_VAR(tmp5,"tmp5");
	HX_STACK_LINE(482)
	::lime::math::Matrix3 tmp6 = ::lime::math::Matrix3_obj::__new(tmp,tmp1,tmp2,tmp3,tmp4,tmp5);		HX_STACK_VAR(tmp6,"tmp6");
	HX_STACK_LINE(482)
	return tmp6;
}


HX_DEFINE_DYNAMIC_FUNC0(Matrix_obj,__toMatrix3,return )

Void Matrix_obj::__transformInversePoint( ::openfl::geom::Point point){
{
		HX_STACK_FRAME("openfl.geom.Matrix","__transformInversePoint",0xde42fb73,"openfl.geom.Matrix.__transformInversePoint","openfl/geom/Matrix.hx",487,0xa0d54189)
// …

Видно, что идёт сначала тело метода, потом макросом конструируется динамическая обёртка, потом следующий метод, потом динамическая обёртка для него и так далее. Поскольку обёрткам имена на первом этапе дали, а собственно методам — ещё нет, в списке подпрограмм IDA должна получиться «полосатая» картина, когда именованные подпрограммы перемежаются с именованными.

Это не вполне так, но на этом этапе нужно обработать только самые очевидные случаи — когда между динамическими обёртками ровно одна подпрограмма, и, скорее всего, это и есть метод. Ему даётся имя по обёртке, которая ниже него.

Осторожно: попадались случаи, но не в этом файле, когда IDA не распознавала тело метода как подпрограмму, зато распознавала что-то вспомогательное, идущее после метода. На настоящий метод идёт обратная ссылка из VMT.

Третий этап: когда между обёртками две подпрограммы

Динамические обёртки создаются макросом, который выглядит так:

#define HX_DEFINE_DYNAMIC_FUNC0(class,func,ret) 
 ::Dynamic __##class##func(hx::Object *inObj) 
{ 
      ret reinterpret_cast<class *>(inObj)->func(); return  ::Dynamic(); 
}; 
 ::Dynamic class::func##_dyn() 
{
   return hx::CreateMemberFunction0(this,__##class##func); 
}

Как можно видеть, обёрток здесь создаётся сразу две, типизированная и нетипизированная, но типизированная обычно выбрасывается транслятором C++ за ненадобностью. Если между динамическими обёртками сразу две безымянных подпрограммы, то, скорее всего, первая из них — это нужный метод, а вторая — типизированная обёртка.

К началу третьего этапа большая часть методов уже должна быть проименована, так что если смотреть из VMT, то это будут одиночные пробелы, и на данном этапе они устраняются.

Четвёртый этап: закрываем большие пробелы в VMT

Бывает, остаются большие пробелы в VMT, по два и более метода. В очередной раз можно отметить удобство взгляда из VMT. Так, если при обходе __Field упустить один метод, он будет выглядеть в списке подпрограмм IDA как три безымянные подпрограммы между динамическими обёртками, но хаКс может генерировать дополнительные подпрограммы и для других нужд, и тогда тоже может получаться три безымянные подпрограммы между динамическими обёртками.

Из VMT же видно: если пробел из двух элементов, значит, это упущенная динамическая обёртка в __Field. Находим в списке подпрограмм, где этот пробел, переходим к средней подпрограмме, это должна быть обёртка. С помощью X открываем список обратных ссылок, среди них должен быть __Field. Переходим туда, выясняем имя обёртки, пробел в списке подпрограмм «затягивается» полосой, и дальше по описанному алгоритму проставляем имена методов.

Методы hx.Object

Для полноты можно открыть hxcpp/include/hx/Object.h, выписать оттуда все виртуальные методы по порядку, и так идентифицировать методы в начале VMT.

Определение типов данных полей и аргументов

Когда у полей и аргументов вызываются методы (как и все, виртуальные), надо понять, в какой VMT их искать, а для этого надо понять, каких вообще они типов. Если не запускать отладчик, помогают это сделать динамические обёртки. На вход они получают аргументы формальных типов (Dynamic, Dynamic, Dynamic, … ) и, чтобы сделать вызов, они сначала приводят Dynamic к реальному ожидаемому методом типу. Во время этого преобразования как раз можно и узнать эти самые типы.

Например, если в теле обёртки видим:

.text:010B3884                 LDR             R1, =(off_23DE1D4 - 0x10B388E)
.text:010B3886                 MOVS            R3, #0
.text:010B3888                 LDR             R2, =(off_23E04A0 - 0x10B3890)
.text:010B388A                 ADD             R1, PC ; off_23DE1D4
.text:010B388C                 ADD             R2, PC ; off_23E04A0
.text:010B388E                 LDR             R1, [R1] ; hx_Object_ci
.text:010B3890                 LDR             R2, [R2] ; off_22D9DE0
.text:010B3892                 BLX.W           __dynamic_cast

…то видно, что делается приведение из hx.Object во что-то другое. Если hx_Obejct_ci у вас ещё не идентифицирован, то оба класса будут неизвестные, но это решаемо. Смотрим, в чей RTTI ведут указатели (в данном примере off_22D9DE0), проставляем имена, делаем выводы.

Аналогично для полей пригождается __SetField, который вынужден приводить тип от Dynamic к реальному типу поля, тем самым давая подсказку.

Статические поля и методы

Если у класса есть статические элементы, у него переопределяются статические методы __GetStatic и/или __SetStatic. В VMT их по понятным причинам не видно, но если в классе есть одновременно и статические, и обычные элементы, то в транслированном коде идут по порядку __Field, __GetStatic, __SetField, __SetStatic, так что, зная, где __Field и __SetField, можно вычислить __GetStatic и __SetStatic рядом с ними. Там так же в начале switch по длине строки, и затем операции сравнения.

Скринкаст

00:00 Находим __Field и __SetField
03:00 Первый этап: ищем динамические обёртки
21:30 Второй этап: самые простые случаи
30:48 Третий этап: когда между обёртками две подпрограммы
33:15 Четвёртый этап: закрываем большие пробелы в VMT
49:00 Методы hx.Object

Автор: Иван Левашев

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js