Хакер - Get your Xojo. Реверсим приложение на REALbasic
hacker_frei
МВК
Помнишь, как на школьных уроках информатики тебя заставляли изучать Basic? Так вот: это был ненастоящий Basic. А настоящий, то есть REALbasic, теперь называется Mojo Xojo, и на нем до сих пор пишут приложения. Сегодня я расскажу тебе, как они устроены изнутри и как их можно взломать.
Часто реверсить программный продукт сложно не потому, что его код запутан или на него навесили какую‑то особенную защиту, а потому, что разработчики использовали редкий и малораспространенный фреймворк. Сегодня в нашем меню — экзотическая среда разработки под названием REALbasic (Xojo).
Это один из пионеров кросс‑платформенного программирования. REALbasic неоднократно менял название, архитектуру и хозяев и в последнее время серьезно растерял как свои рыночные позиции, так и актуальность. Написанные на нем приложения встречаются все реже и реже, в основном в таких узкоспециализированных областях, как колориметрия. Из‑за малой распространенности для него, в отличие от известных сред разработки (вроде Delphi, .NET или VBS), практически отсутствуют специализированные инструменты для реверса. Поэтому мы, как обычно, на примере конкретных приложений разберем принципы и лайфхаки для изучения кода таких приложений.
Начнем с самого простого случая. Как ни странно, легче всего исследовать приложение, созданное в современных актуальных версиях Xojo (да‑да, он мало того что еще поддерживается, вдобавок стал 64-битным, Википедия врет).
Итак, нам попалось графическое приложение, при анализе которого наш безотказный Detect It Easy (DIE) утверждает, что это Xojo (x64).

Нам понадобится самая малость: включить функции приложения, которые отказываются работать в незарегистрированной версии, выдавая вместо этого окошки с требованием регистрации. Загрузив программу в отладчик x64dbg и притормозив ее в этом месте, мы с облегчением замечаем, что программа не зашифрована, не упакована и лишена средств антиотладки. Она даже сообщение выдает стандартным MessageBoxA, при этом программа прекрасно дизассемблируется при помощи IDA. Полученный код, правда, как и следовало ожидать от кросс‑платформенного бейсика, чудовищно неуклюж и сложен для понимания.

Хочется как‑то облегчить себе жизнь, хотя бы восстановив имена вызываемых методов. Наш предыдущий опыт работы со скомпилированным кодом, изначально предназначенным под интерпретатор, подсказывает, что эта информация где‑то обязательно должна храниться. Просмотрев в IDA все ссылки на процедуру, из которой была вызвана ошибка демоверсии, находим интересную конструкцию:
...
.text:0000000141A85CF5 mov edx, 0F40h
.text:0000000141A85CFA xor r8d, r8d
.text:0000000141A85CFD lea rcx, sub_140D7D410
.text:0000000141A85D04 lea r9, aOBevelbutton_64 ; "%%o<BevelButton>" — похоже на класс нашего метода
.text:0000000141A85D0B lea r10, aBvlmeaschartAc_0 ; "BvlMeasChart_Action" — похоже на его имя
.text:0000000141A85D12 lea r11, sub_141A91350 ; Адрес нашего метода
.text:0000000141A85D19 mov [rbp+8F0h+var_438], rax
.text:0000000141A85D20 mov [rbp+8F0h+var_60], r11
.text:0000000141A85D27 mov rax, [rbp+8F0h+var_8E0]
.text:0000000141A85D2B mov [rax+0F68h], r11
.text:0000000141A85D32 mov r11, [rbp+8F0h+var_68]
.text:0000000141A85D39 mov [r11+0F40h], r10
.text:0000000141A85D40 mov r10, [rbp+8F0h+var_68]
.text:0000000141A85D47 mov [r10+0F60h], r9
.text:0000000141A85D4E mov [rbp+8F0h+var_440], rcx
.text:0000000141A85D55 mov r9, [rbp+8F0h+var_68]
.text:0000000141A85D5C mov [r9+0F70h], rcx
.text:0000000141A85D63 mov rcx, [rbp+8F0h+var_68]
.text:0000000141A85D6A mov dword ptr [rcx+0F78h], 39h ; '9'
.text:0000000141A85D74 mov rcx, [rbp+8F0h+var_68]
.text:0000000141A85D7B mov dword ptr [rcx+0F48h], 101h
.text:0000000141A85D85 mov rcx, [rbp+8F0h+var_68]
.text:0000000141A85D8C mov [rbp+8F0h+var_8E8], rcx
.text:0000000141A85D90 call RuntimeAllocateAttributeTable
...
Бегло просмотрев код, обнаруживаем великое множество подобных конструкций по всему дизассемблированному приложению. Очевидно, что это инициализация каких‑то внутренних REALbasic-овских таблиц методов. Вероятнее всего, за это ответственна функция RuntimeAllocateAttributeTable из модуля XojoGUIFramework64.dll, вызывает сомнение только способ передачи параметров. Вроде как строки и адреса перед вызовом RuntimeAllocateAttributeTable записываются в стек, однако не в область параметров, а в область локальных переменных, причем по совершенно разным адресам. Попробуем проанализировать еще пару подобных конструкций:
.text:0000000141A85D95 mov edx, 0F80h <-- Обрати внимание, что число в edx 0F80h
.text:0000000141A85D9A xor r8d, r8d
.text:0000000141A85D9D lea rcx, sub_140D7D410
.text:0000000141A85DA4 lea r9, aOBevelbutton_65 ; "%%o<BevelButton>"
.text:0000000141A85DAB lea r10, aBvldefchartAct ; "BvlDefChart_Action"
.text:0000000141A85DB2 lea r11, sub_141A95B20
.text:0000000141A85DB9 mov [rbp+8F0h+var_448], rax
.text:0000000141A85DC0 mov [rbp+8F0h+var_60], r11
.text:0000000141A85DC7 mov rax, [rbp+8F0h+var_8E8]
.text:0000000141A85DCB mov [rax+0FA8h], r11
.text:0000000141A85DD2 mov r11, [rbp+8F0h+var_68]
.text:0000000141A85DD9 mov [r11+0F80h], r10 <-- Везде эквивалентно смещению, по которому сохраняется адрес строки имени метода
.text:0000000141A85DE0 mov r10, [rbp+8F0h+var_68]
.text:0000000141A85DE7 mov [r10+0FA0h], r9
.text:0000000141A85DEE mov [rbp+8F0h+var_450], rcx
.text:0000000141A85DF5 mov r9, [rbp+8F0h+var_68]
.text:0000000141A85DFC mov [r9+0FB0h], rcx
.text:0000000141A85E03 mov rcx, [rbp+8F0h+var_68]
.text:0000000141A85E0A mov dword ptr [rcx+0FB8h], 3Ah ; ':'
.text:0000000141A85E14 mov rcx, [rbp+8F0h+var_68]
.text:0000000141A85E1B mov dword ptr [rcx+0F88h], 101h
.text:0000000141A85E25 mov rcx, [rbp+8F0h+var_68]
.text:0000000141A85E2C mov [rbp+8F0h+var_8F0], rcx <-- Относительно адреса в RCX
.text:0000000141A85E30 call RuntimeAllocateAttributeTable
Вот еще один пример:
.text:0000000141A85E35 mov edx, 0FC0h
.text:0000000141A85E3A xor r8d, r8d
.text:0000000141A85E3D lea rcx, sub_140D7D410
.text:0000000141A85E44 lea r9, aOBevelbutton_66 ; "%%o<BevelButton>"
.text:0000000141A85E4B lea r10, aBvlgraph2dActi_0 ; "BvlGraph2D_Action"
.text:0000000141A85E52 lea r11, sub_141A95D70
.text:0000000141A85E59 mov [rbp+8F0h+var_458], rax
.text:0000000141A85E60 mov [rbp+8F0h+var_60], r11
.text:0000000141A85E67 mov rax, [rbp+8F0h+var_8F0]
.text:0000000141A85E6B mov [rax+0FE8h], r11 <-- а относительные смещения между адресом метода
.text:0000000141A85E72 mov r11, [rbp+8F0h+var_68]
.text:0000000141A85E79 mov [r11+0FC0h], r10 <-- именем метода
.text:0000000141A85E80 mov r10, [rbp+8F0h+var_68]
.text:0000000141A85E87 mov [r10+0FE0h], r9 <-- и именем класса в каждой конструкции одинаковы
.text:0000000141A85E8E mov [rbp+8F0h+var_460], rcx
.text:0000000141A85E95 mov r9, [rbp+8F0h+var_68]
.text:0000000141A85E9C mov [r9+0FF0h], rcx
.text:0000000141A85EA3 mov rcx, [rbp+8F0h+var_68]
.text:0000000141A85EAA mov dword ptr [rcx+0FF8h], 3Bh ; ';'
.text:0000000141A85EB4 mov rcx, [rbp+8F0h+var_68]
.text:0000000141A85EBB mov dword ptr [rcx+0FC8h], 101h
.text:0000000141A85EC5 mov rcx, [rbp+8F0h+var_68]
.text:0000000141A85ECC mov [rbp+8F0h+var_8F8], rcx
.text:0000000141A85ED0 call RuntimeAllocateAttributeTable
Немного пораскинув мозгами, мы приходим к выводу, что на входе в RuntimeAllocateAttributeTable в регистре RCX находится указатель на некую таблицу, каждый элемент которой занимает 0x40 байт и имеет примерно следующую структуру:
DQ MethodName ;RCX[RDX+0]
DQ ?
DQ ?
DQ ?
DQ ?
DQ ?
DQ ?
DQ ?
DQ ClassName ;RCX[RDX+20h]
DQ MethodAddress ;RCX[RDX+28h]
DQ ?
DQ ?
DQ ?
DQ ?
DQ ?
DQ ?
А в регистре RDX хранится относительное смещение на текущий элемент данной таблицы в байтах. В принципе, поковырявшись в коде и отладчике, при желании можно раскопать сакральный смысл и остальных полей этой таблицы, но мы не будем отвлекаться: полученной информации нам уже почти достаточно для дальнейшего исследования структуры кода.
Итак, ставим условную точку останова на функцию RuntimeAllocateAttributeTable со следующим текстом журнала:
ClassName: {s:[rcx+rdx+0x20]} MethodName: {s:[rcx+rdx]} Address: {[rcx+rdx+0x28]} called at {[rsp]}
Последний параметр, адрес вызова, нам необходим на всякий случай, чтобы отследить место, в котором что‑то пошло не так при вызове. Перезагружаем программу и — бинго! — в журнале x64dbg видим не идеальный, но относительно вменяемый список имен методов с адресами:
...
ClassName: "%%sp" MethodName: "_AddMenuHandler" Address: 140296DF0 called at 141A84A96
ClassName: "%o<Object>%s" MethodName: "_ControlByName" Address: 140296E70 called at 141A84B3C
ClassName: "%o<Object>%si8" MethodName: "_ControlByNameIndex" Address: 140296F60 called at 141A84BE2
ClassName: "%%" MethodName: "_DependencyList" Address: 140297050 called at 141A84C88
ClassName: "%o<Object>%s" MethodName: "_GetControl" Address: 140296E70 called at 141A84D1D
ClassName: "%o<Object>%si8" MethodName: "_GetControlArray" Address: 140296F60 called at 141A84DB2
ClassName: "%%i8i8i8i8" MethodName: "_Move" Address: 1402970F0 called at 141A84E58
ClassName: "%%" MethodName: "__Exit" Address: 140297180 called at 141A84EED
ClassName: "%%" MethodName: "__Init" Address: 1402971D0 called at 141A84F82
ClassName: "%o<Object>%p" MethodName: "__New" Address: 140297240 called at 141A85028
ClassName: "%%" MethodName: "Layout" Address: 141A86480 called at 141A850CE
ClassName: "%%" MethodName: "CreateControls" Address: 140775830 called at 141A85174
ClassName: "%%" MethodName: "_CreateControls0" Address: 141A86530 called at 141A85216
ClassName: "%o<BevelButton>%" MethodName: "BvlPaper" Address: 141A8A830 called at 141A852E6
ClassName: "%o<BevelButton>%" MethodName: "BvlMetamerism" Address: 141A8A9F0 called at 141A8538C
ClassName: "%o<BevelButton>%" MethodName: "BvlGamut" Address: 141A8ABB0 called at 141A85432
ClassName: "%o<BevelButton>%" MethodName: "BvlMeasSpec" Address: 141A8AD70 called at 141A854D5
ClassName: "%o<BevelButton>%" MethodName: "BvlMeasChart" Address: 141A8AF30 called at 141A85575
ClassName: "%o<BevelButton>%" MethodName: "BvlDefChart" Address: 141A8B0F0 called at 141A85615
ClassName: "%o<BevelButton>%" MethodName: "BvlGraph2D" Address: 141A8B2B0 called at 141A856B5
ClassName: "%o<BevelButton>%" MethodName: "BvlLight" Address: 141A8B470 called at 141A85755
ClassName: "%o<BevelButton>%" MethodName: "BvlCalculate" Address: 141A8B630 called at 141A857F5
ClassName: "%o<BevelButton>%" MethodName: "BvlDifference" Address: 141A8B7F0 called at 141A85895
ClassName: "%o<BevelButton>%" MethodName: "BvlSearch" Address: 141A8B9B0 called at 141A85935
ClassName: "%o<BevelButton>%" MethodName: "BvlMakeLightSource" Address: 141A8BB70 called at 141A859D5
ClassName: "%o<BevelButton>%" MethodName: "BvlMakeChartImage" Address: 141A8BD30 called at 141A85A75
...
К сожалению, суть этого метода состоит в том, что он чисто динамический, то есть нельзя получить список имен просто из дизассемблера, не запустив программу. Но если тебя вдруг настигнет приступ перфекционизма, можно просто распарсить этот список и скриптом расставить в IDA или x64dbg метки с именами методов по нужным адресам.
Нам же некогда отвлекаться на подобные мелочи. Пока вполне достаточно того, что при заходе в каждую процедуру, которую IDA не детектит как стандартную бейсиковскую, мы можем найти ее имя по адресу в полученном списке. Если, конечно, она там вообще находится, а такое тоже бывает. В любом случае у нас уже есть неплохой инструмент, благодаря которому за десять минут работы с отладчиком и IDA мы обнаруживаем и патчим все проверки на жадность в программном модуле и готовы продолжать дальнейшее знакомство с особенностями Xojo.
Чтобы немного усложнить себе жизнь, рассмотрим чуть более старый, 32-битный вариант REALbasic. Итак, у нас есть другая программа, загрузка которой в DIE показывает следующее окошко.

Казалось бы, 32-битная версия Xojo должна быть проще, ан нет. Загрузив приложение в IDA, мы с удивлением обнаруживаем практически полное отсутствие кода и импорта при внушительном размере EXE-модуля.

Разгадка проста: практически весь EXE-модуль состоит из огромного оверлея, который подгружается на произвольные адреса во время загрузки программы. А значит, даже дампить для изучения уже загруженный модуль особого смысла нет, хотя сама программа не мешает ни отладке, ни дампу. Погуглив, обнаруживаем, что более старую версию нашего фреймворка народ уже изучал и даже запилил на ее основе питоновский скрипт под IDA, который парсит оверлей.
Желающие могут ознакомиться с этим скриптом самостоятельно, я же не стану останавливаться на нем подробно, поскольку скрипт на нашем модуле не работает, а структура оверлея немного отличается от описанной в упомянутой статье. Будь у нас в запасе побольше времени, мы бы могли разобрать новую структуру по винтикам, понять, что изменилось, и создать на основе старого новый работоспособный скрипт, поддерживающий наш формат. Но мы пойдем другим путем.
Помнишь, как мы выкрутились в случае 64-битной версии? Повесили бряк на функцию RuntimeAllocateAttributeTable из модуля XojoGUIFramework64.dll и ловили им инициализацию каждого метода. Разумеется, при загрузке в отладчик программы в ней нет никакого импорта, кроме нескольких базовых функций kernel32, необходимых для загрузки оверлея. Однако, прервавшись в отладчике внутри уже загруженной программы, мы видим необходимый модуль в списке — здесь он называется XojoGUIFramework32.dll, и необходимая функция RuntimeAllocateAttributeTable там есть.
Ставим на нее бряк и перезагружаем программу в отладчике. Бряк стопится уже при вызове из загруженного оверлея, код вызова RuntimeAllocateAttributeTable до боли похож на аналогичный из 64-битного модуля. Поскольку мы уже в курсе, как все было устроено там, нам не составит большого труда придумать аналогию для каждого нужного нам поля:
0197FCA6 mov esp,dword ptr ss:[ebp-E4]
0197FCAC mov dword ptr ss:[ebp-D0],eax
0197FCB2 mov ecx,198074F
0197FCB7 mov dword ptr ss:[ebp-58],ecx
0197FCBA mov edx,dword ptr ss:[ebp-50]
0197FCBD mov dword ptr ds:[edx+248],ecx
0197FCC3 mov ebx,1AA2296 <-- Адрес класса
0197FCC8 mov dword ptr ss:[ebp-58],ebx
0197FCCB mov dword ptr ds:[edx+250],ebx
0197FCD1 mov ecx,377907D <-- Имя метода
0197FCD6 mov dword ptr ds:[edx+258],ecx
0197FCDC mov esi,3774A0C <-- Имя класса
0197FCE1 mov dword ptr ds:[edx+268],esi
0197FCE7 xor edi,edi
0197FCE9 mov dword ptr ds:[edx+26C],edi
0197FCEF mov esi,11
0197FCF4 mov dword ptr ds:[edx+25C],esi
0197FCFA push eax
0197FCFB push 0
0197FD00 push 258 <-- Смещение до элемента таблицы
0197FD05 push edx <-- Адрес таблицы
0197FD06 call <xojoguiframework32.RuntimeAllocateAttributeTable>
Судя по всему, эта структура в 32-битном исполнении занимает 0x28 байт и состоит из десяти полей. Несложно переделать текст журнала в условной точке останова под новую информацию. Что я и предлагаю читателю сделать самостоятельно.
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei