Хакер - Препарируем P-Code. Как реверсить старый софт на Visual Basic

Хакер - Препарируем P-Code. Как реверсить старый софт на Visual Basic

hacker_frei

https://t.me/hacker_frei

МВК 

Что­бы нем­ного раз­нооб­разить свою жизнь, сегод­ня мы сно­ва зай­мем­ся раз­боркой антиква­риата. Мно­гие уже забыли о сущес­тво­вании такого инс­тру­мен­та, как дав­но похоро­нен­ный P-Code, в который тран­сли­рова­лись прог­раммы на Visual Basic. Тем не менее соз­данные с его помощью при­ложе­ния до сих пор сущес­тву­ют, и сегод­ня мы с тобой акку­рат­но сло­маем одно из них.

WARNING

Статья име­ет озна­коми­тель­ный харак­тер и пред­назна­чена для спе­циалис­тов по безопас­ности, про­водя­щих тес­тирова­ние в рам­ках кон­трак­та. Автор и редак­ция не несут ответс­твен­ности за любой вред, при­чинен­ный с при­мене­нием изло­жен­ной информа­ции. Рас­простра­нение вре­донос­ных прог­рамм, наруше­ние работы сис­тем и наруше­ние тай­ны перепис­ки прес­леду­ются по закону.

К сожале­нию (или счастью?), Microsoft поэтап­но похоро­нила P-Code, спер­ва перей­дя на ком­пиляцию пи‑кода в натив, от чего, на мой взгляд, он стал силь­но более неук­люжим, а затем и вов­се пол­ностью переве­ла VB на плат­форму .NET. Тем не менее из‑за сво­ей прос­тоты и популяр­ности в былые вре­мена соз­данные на P-Code про­екты весь­ма мно­гочис­ленны и про­дол­жают работать по сей день.

Что­бы ты не испу­гал­ся, если вдруг в твои руки попадет подоб­ный раритет, знал, что с ним делать и с какой сто­роны к нему подой­ти, рас­смот­рим спе­цифи­ку и внут­реннее устрой­ство P-Code на при­мере доработ­ки неболь­шой гра­фичес­кой прог­раммы. Усло­вия задачи таковы: у нас есть соф­тина, в текущей пос­тавке которой отклю­чены некото­рые полез­ные фун­кции (нет свя­зан­ных с ними пун­ктов меню). Но сама чер­ная кош­ка в тем­ной ком­нате при­сутс­тву­ет, то есть в модуле име­ются и нуж­ные нам пун­кты меню, и код отве­чающих за них фун­кций. Прос­то они невиди­мы для нашей лицен­зии, и открыть их — наша задача.

Для начала опре­деля­ем тип прог­раммы и ее защиту при помощи DetectItEasy.

DIE опре­делил ком­пилятор прог­раммы как Microsoft Visual Basic 6.0

По­пыт­ка заг­рузить иссле­дуемую прог­рамму в IDA, к сожале­нию, не при­нес­ла никако­го полез­ного резуль­тата.

IDA счи­тает, что натив­ный код отсутс­тву­ет или зашиф­рован

Нас­тало вре­мя для нашего любимо­го отладчи­ка x64dbg. Нем­ного повозив­шись в нем, обна­ружи­ваем, что основной код кру­тит­ся внут­ри модуля msvbvm60.dll.

Прог­рамма в отладчи­ке

На­лицо клас­сичес­кая вир­туаль­ная машина. В ней интер­пре­тиру­ется поток дан­ных, текущий ука­затель коман­ды находит­ся в регис­тре esi. Из потока выбира­ется байт кода опе­рации, и по его индексу из таб­лицы адре­сов обра­бот­чиков (6909AA24 в дам­пе) выбира­ется адрес обра­бот­чика сле­дующей коман­ды, на которую дела­ется переход. По счастью, это не злоб­ный про­тек­тор типа Enigma или Themida, никаких обфуска­торов и анти­отладчи­ков в коде нет. Более того, нам даже не тре­бует­ся изоб­ретать велоси­пед, раз­бирая вир­туаль­ную машину самос­тоятель­но — за вре­мя сущес­тво­вания VB за нас это сде­лали дру­гие доб­рые люди.

Итак, мы наконец вплот­ную подош­ли к понятию P-Code. Этот тер­мин был при­думан еще пол­века назад одним из осно­вопо­лож­ников сов­ремен­ного прог­рамми­рова­ния Ник­лаусом Вир­том для любого аппа­рат­но‑незави­симо­го псев­докода вир­туаль­ной машины. Одна­ко с лег­кой руки Microsoft при­жилось оно при­мени­тель­но к теперь уже мер­тво­му байт‑коду Visual Basic, пре­иму­щес­твен­но 6-й вер­сии.

Схе­ма фун­кци­они­рова­ния P-Code пре­дель­но прос­та и вид­на на скрин­шоте выше. Из стран­ных осо­бен­ностей мож­но отме­тить наличие целых шес­ти таб­лиц обра­бот­чиков байт‑кодов. Это зна­чит, что не все опко­ды псев­докоманд одно­бай­товые, каж­дый из пос­ледних пяти опко­дов глав­ной таб­лицы (FB-FF) — «сос­тавной» двух­бай­товый. То есть обра­бот­чик каж­дого из этих кодов счи­тыва­ет сле­дующий байт и переда­ет управле­ние по его индексу на обра­бот­чик из дру­гой таб­лицы адре­сов, в которой тоже содер­жится 256 воз­можных зна­чений. Непонят­но, зачем исполь­зует­ся такой хит­рый спо­соб, а глав­ное — для чего пре­дус­мотрен такой запас опко­дов, ведь из 1531 воз­можно­го задей­ство­вано все­го 822, при­чем некото­рые дуб­лиру­ют друг дру­га.

Я не буду при­водить здесь пол­ную таб­лицу опко­дов P-Code, жела­ющие могут най­ти ее на сай­те DotFix или даже в более информа­тив­ном виде в ис­ходни­ках про­екта Semi-VB-Decompiler. Там же при­водят­ся аргу­мен­ты и при­мер­ный смысл каж­дого опко­да.

Во­ору­жив­шись дан­ной информа­цией, гораз­до про­дук­тивнее раз­бирать и отла­живать прог­рамму даже в x64dbg. Одна­ко сущес­тву­ют и еще более прод­винутые инс­тру­мен­ты для отладки и деком­пиляции пи‑кода. Два из них ты уже, навер­ное, уви­дел по при­веден­ным выше ссыл­кам: сво­бод­ный деком­пилятор Semi-VB-Decompiler и ком­мерчес­кий VB Decompiler. Еще один незаме­нимый инс­тру­мент для работы с пи‑кодом — сво­бод­ный ди­зас­сем­блер‑отладчик VBDec.

На­до ска­зать, что эти про­екты весь­ма сырые и име­ют мас­су недоче­тов (к при­меру, Semi-VB-Decompiler у меня вооб­ще не деком­пилиро­вал ни одно­го фай­ла и не смог най­ти валид­ную сиг­натуру), но их воз­можнос­ти допол­няют друг дру­га. К при­меру, VB Decompiler уме­ет с более‑менее перемен­ным успе­хом не толь­ко дизас­сем­бли­ровать, но и вос­ста­нав­ливать VB-код. При­чем не толь­ко из P-Code, но и из ском­пилиро­ван­ного из него натива, и даже из .NET. А хоть VBDec это­го и не уме­ет, зато его дизас­сем­блер на нес­коль­ко поряд­ков более пря­мой и без­глюч­ный, а глав­ное, у него есть пол­нофун­кци­ональ­ный дебаг­гер с воз­можностью трас­сиров­ки P-Code.

Вер­немся к нашей задаче и поп­робу­ем решить ее уже с помощью этих чудес­ных инс­тру­мен­тов. Для начала откры­ваем иссле­дуемую прог­рамму в VB Decompiler и ищем глав­ное окно. В VB Decompiler для это­го име­ется инс­тру­мент Tools-Search string (он, прав­да, вре­мена­ми под­глю­чива­ет, но потер­петь мож­но). Хоть прог­рамма и обфусци­рова­на, но мы находим и глав­ное окно MDIForm1, и пер­вый пункт меню из скры­тых из‑за отсутс­твия лицен­зии.

Фор­ма основно­го окна, вид из VB Decompiler

Те­перь, ког­да нам извес­тно имя иден­тифика­тора нуж­ного пун­кта меню, мы можем при помощи того же инс­тру­мен­та (поиск строк) поис­кать его уже по коду. Очень быс­тро мы обна­ружи­ваем его упо­мина­ние в нуж­ном нам кон­тек­сте:

loc_930D48: var_19C(&HFF).Visible = var_1BC

loc_930D50: GoTo loc_930D97

loc_930D53: End If

loc_930D63: var_D0(&HFF).Visible = MemVar_1619316

loc_930D79: var_15C(&HFF).Visible = var_B0

loc_930D8F: var_C0(&HFF).Visible = var_16C

loc_930D97: ' Referenced from: 930D50

loc_930DA9: If (MemVar_161C794(&HCA) = "1") Then

loc_930DBA: xy0000.Visible = var_E0 <-------------------------

loc_930DC2: GoTo loc_930DDD

loc_930DC5: End If

loc_930DD5: xy0000.Visible = var_19C <-------------------------

loc_930DDD: ' Referenced from: 930DC2

loc_930DEF: If (MemVar_161C794(&HAA) = "1") Then

loc_930E00: var_D0(&HFF).Visible = MemVar_16182F8

loc_930E08: GoTo loc_930E23

loc_930E0B: End If

На­до ска­зать, код из‑за обфуска­ции получа­ется доволь­но‑таки безум­ный, поэто­му нашему деком­пилято­ру сры­вает кры­шу. Что­бы про­яснить ситу­ацию, откры­ваем зак­ладку Disassembler (при этом, к нашей великой досаде, сби­вает­ся текущее сме­щение в фай­ле, и при­ходит­ся его заново искать в про­цеду­ре). Логика ста­новит­ся чуть яснее:

loc_930D9B: F5CA000000 LitI4 &HCA

loc_930DA0: 051100 ImpAdLdRf MemVar_161C794

loc_930DA3: 9E Ary1LdI4 <-------------- MemVar_161C794(&HCA)

loc_930DA4: 1B6B00 LitStr "1"

loc_930DA7: FB30 EqStr

loc_930DA9: 1C3D1A BranchF loc_930DC5 <----- Если не равно 1, то переход на 930DC5

loc_930DAE: F4FF LitI2_Byte &HFF <----- FF = True

loc_930DB0: 21 FLdPrThis

loc_930DB1: 0F0004 VCallAd Control_ID_

loc_930DB4: 1978FF FStAdFunc var_88

loc_930DB7: 0878FF FLdPr var_88

loc_930DBA: 0D5C008B00 Me.Visible = <----- xy0000.Visible = True

loc_930DBF: 1A78FF FFree1Ad var_88

loc_930DC2: 1E551A Branch loc_930DDD

loc_930DC5: End If

loc_930DC9: F4FF LitI2_Byte &H00 <----- 00 = False

loc_930DCB: 21 FLdPrThis

loc_930DCC: 0F0004 VCallAd Control_ID_

loc_930DCF: 1978FF FStAdFunc var_88

loc_930DD2: 0878FF FLdPr var_88

loc_930DD5: 0D5C008B00 Me.Visible = <----- xy0000.Visible = False

loc_930DDA: 1A78FF FFree1Ad var_88

Во­обще говоря, если вни­матель­но прис­мотреть­ся, мы уви­дим, что даже в дизас­сем­бли­рова­нии этот деком­пилятор слег­ка лажа­ет. К при­меру, замет­но, что по сме­щению 930DC5 он сож­рал целых 4 бай­та кода. Воз­можно, это мне попада­лись глюч­ные вер­сии (я про­верял все­го пару штук), но с VB Decompiler нуж­но дер­жать ухо вос­тро: такие мел­кие баги попада­ются на каж­дом шагу и силь­но раз­дра­жают. Дру­гой деком­пилятор, VBDec, с дизас­сем­бли­рова­нием справ­ляет­ся гораз­до луч­ше. Но в нем нет вос­ста­нов­ления исходно­го кода, ибо нет в мире совер­шенс­тва. Вот тот же учас­ток кода, вос­ста­нов­ленный через VDec: явно вид­но, что VB Decompiler наг­ло халявит, про­пус­кая все инс­трук­ции BoS (LargeBos).

930D9B F5 CA000000 LitI4 0xCA

930DA0 05 1100 ImpAdLdRf unk_161C794

930DA3 9E Ary1LdI4

930DA4 1B 6B00 LitStr str_4CC410='1'

930DA7 FB30 EqStr

930DA9 1C 3D1A BranchF loc_930DC5

930DAC 00 16 BoS loc_930DC2

930DAE F4 FF LitI2_Byte 255

930DB0 21 FLdPrThis

930DB1 0F 0004 VCallAd aym00000.xy0000

930DB4 19 78FF FStAdFunc var_88

930DB7 08 78FF FLdPr var_88

930DBA 0D 5C008B00 VCallHresult _Menu Let Visible

930DBF 1A 78FF FFree1Ad var_88

930DC2 1E 551A Branch loc_930DDD

930DC5 00 02 BoS loc_930DC7

930DC7 00 16 BoS loc_930DDD

930DC9 F4 FF LitI2_Byte 0

930DCB 21 FLdPrThis

930DCC 0F 0004 VCallAd aym00000.xy0000

930DCF 19 78FF FStAdFunc var_88

930DD2 08 78FF FLdPr var_88

930DD5 0D 5C008B00 VCallHresult _Menu Let Visible

930DDA 1A 78FF FFree1Ad var_88

Итак, мы наш­ли иско­мое мес­то в коде, теперь его неп­лохо бы про­пат­чить. Здесь у нас име­ется два пути: либо поп­равить на True аргу­мент коман­ды LitI2_Byte по адре­су 930DC9, дабы пункт меню оста­вал­ся видимым при любом резуль­тате про­вер­ки, либо убрать саму про­вер­ку. К нашему ужа­су, и здесь мы ока­зыва­емся в боль­шой беде: у пи‑кода вне­зап­но нет одно­бай­товой коман­ды NOP, которой мы при­вык­ли забивать уби­раемый код в дру­гих сис­темах. При­дет­ся вык­ручивать­ся околь­ными путями, отдель­ные из которых могут быть весь­ма ори­гиналь­ными. К при­меру, David Zimmer пред­лага­ет самому сконс­тру­иро­вать NOP из коман­ды InvalidExcode (опкод 1), заменив ее обра­бот­чик в таб­лице чуть модифи­циро­ван­ным адре­сом обра­бот­чика StAry:

call ___vbaAryMove@8 ; Оригинальный обработчик StAry

xor eax, eax ; Новый адрес обработчика для псевдокоманды InvalidExcode, который превращает ее в NOP

mov al, [esi]

inc esi

jmp ds:_tblByteDisp[eax*4]

К сло­ву ска­зать, отладчик VBDec исполь­зует имен­но такой метод для заноп­ливания кода, называя новую инс­трук­цию vbDecNop.

При всей сво­ей ори­гиналь­нос­ти опи­сан­ный выше спо­соб пред­полага­ет пат­чинг сис­темной биб­лиоте­ки msvbvm60.dll, что край­не нежела­тель­но делать ради такой мелочи, как соз­дание одно­бай­тового NOP. По счастью, одно­бай­товый NOP и не тре­бует­ся, ведь сущес­тву­ет двух­бай­товый NOP — это псев­докоман­да CI2UI1 (код FC14). B качес­тве же трех­бай­тового NOP мож­но исполь­зовать коман­ду безус­ловно­го перехо­да Branch (1EXXXX, где XXXX — сме­щение до сле­дующей инс­трук­ции отно­ситель­но начала текущей фун­кции).

В нашем слу­чае дос­таточ­но поп­равить сме­щение условно­го перехо­да по адре­су 930DA9. Еще одна весь­ма спе­цифич­ная осо­бен­ность пи‑кода — отсчет сме­щений в псев­докоман­дах перехо­да отно­ситель­но не текуще­го адре­са псев­докоман­ды, а, как я уже говорил, начала текущей фун­кции. В нашем при­мере 92F389 — начало про­цеду­ры, аргу­мент псев­докоман­ды BranchF 1A3D. В ито­ге мы получим адрес перехо­да 92F388+1A3D=930DС5, нам же нужен адрес сле­дующей за BranchF коман­ды 930DAC930DAC-92F388=1A24.

Для пат­ча мож­но исполь­зовать обыч­ный hiew, в нем есть воз­можность ука­зывать не толь­ко сме­щение в фай­ле, но и вир­туаль­ный адрес бай­та при заг­рузке в память (у нас ука­заны имен­но они). Меня­ем байт 3D по адре­су 930DAA на 24 и убеж­даем­ся, что иско­мый пункт меню теперь виден и даже акти­вен. Таким же спо­собом находим и меня­ем код для осталь­ных пун­ктов — задача решена.

Как ты уже понял, за свою дол­гую исто­рию VB и его пи‑код очень неп­лохо иссле­дован. Для его ковыря­ния соз­дано мно­жес­тво раз­ных инс­тру­мен­тов. Поэто­му в зак­лючение я хочу упо­мянуть об одном из них, который, как мне кажет­ся, наибо­лее вос­тре­бован.

Час­тень­ко слу­чает­ся, что надо поменять в прог­рамме тек­сто­вую стро­ку или под­пра­вить эле­мент интерфей­са. Хорошо, если стро­ки или опи­сания эле­мен­тов содер­жатся в стан­дар­тных ресур­сах Windows, для которых сущес­тву­ет мно­жес­тво редак­торов. Одна­ко в EXE-фай­ле, ском­пилиро­ван­ном из VB, сре­ди обыч­ных ресур­сов име­ется толь­ко икон­ка и вер­сия. Собс­твен­ные же ресур­сы VB име­ют свою спе­цифи­ку и отоб­ража­ются толь­ко в опи­сан­ных выше деком­пилято­рах‑дизас­сем­бле­рах. Если же тебе надо что‑либо в них под­пра­вить и неохо­та возить­ся с под­гонкой строк в hiew/winhex, то на помощь при­дет ути­лита VBLocalize. Хотя она доволь­но ста­рая, но при желании ее впол­не мож­но най­ти на прос­торах интерне­та и исполь­зовать для прав­ки отдель­ных строк или даже для пол­ного перево­да интерфей­са прог­раммы, если, конеч­но, на это хва­тит тво­его энту­зиаз­ма.

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei



Report Page