Пример исследования псевдокода VB — HackZona.Ru

Пример исследования псевдокода VB

Пример исследования псевдокода VB

Тип статьи:
Со старой ХакЗоны.
Источник:
1. Немного теории
Под псевдокодом понимается набор опкодов, интерпретируемых в процессе выполнения. Говоря попросту, псевдокод-это общий код машинно-независимого типа, который не может быть интерпретирован непосредственно процессором и требует предварительной трансляции на исконный («native») машинный код. Это немного похоже на компилируемый Java. Ведь для выполнения программ на Java нужна так называемая «виртуальная машина» Java. Такой, казалось бы причудливый термин предполагает наличие транслятора между Java-кодом и машинными кодами процессора.
Почему MS использует псевдокод?.Если определить особый набор инструкций и не распространять информацию о специфике этих инструкций, люди потратят много времени для понимания всех особенностей работы программы. Другое преимущество-уменьшение размера исполняемого кода: определяя размер инструкции точно в один байт, можно создать инструкцию, которая выполняет целую серию операций. Таким образом, код самой программы оказывается маленьким, т. к. она всё время будет подгружать библиотеки с процедурами. Однако эти преимущества вызывают большое сомнение, если обратить внимание на то, как сильно «тормозят» vb-шные программы. Виртуальная машина, транслирующая псевдокод VB в native находится в DLL, которые подгружаются в память процесса всякий раз, как программа, скомпилированная в псевдокод, запускается на выполнение. Эти DLL-
MSVBVM50.DLL и MSVBVM60.DLL.
Расшифровывается как MicroSoft Visual Basic Virtual Machine 5.0 и 6.0 соответственно. Эти две версии имеют различия следующего характера: Версия 6.0 включает новые инструкции и использует более интуитивно понятные названия для инструкций, унаследованных из версии 5.0.
Виртуальная машина VB не только интерпретирует псевдокод VB, эта DLL также используется программами, скомпилированными в native-code, т.к. эта DLL также содержит API-функции, используемые всеми VB-программами. Например, функция rtcMsgBox, которая используется как эквивалент всем известной API MessageBox. Эти функции используются инструкциями псевдокода одинаковым образом, но разными способами, через код, интерпретируемый виртуальной машиной.
Это приводит к серьезной проблеме, когда мы пытаемся трассировать псевдокод:
Ни SoftICE, ни OllyDBG не могут нормально трассировать псевдокод, стоит открыть в одном из этих отладчиков программу, и мы почти сразу же оказываемся внутри виртуальной машины.
Хорошо что сейчас уже придуманы инструменты для анализа и отладки псевдокода, это всем известные ExDec VBExplorer, VBDEcompiler, WKTVBDE, и другие, не заслуживающие особого внимания. Отлаживать p-code позволяет только WKTVBDE от Mr.Silver. Как он это делает? В первых версиях отладчика использовалось некоторое подобие инлайн-патча. Некоторый код как бы инжектировался в MSVBVMxx.DLL непосредственно в процессе выполнения программы; инжектированный код вызывал отладчик, который находится в DLL.
Получалось так, что виртуальная машина прерывалась, отладчик вставал между виртуальной машиной и выполняемым процессом. В дальнейшем авторы программы отступили от этого способа отладки, но общий принцип остался тем же.
Проинжектированный код берет контроль над процессом и дает сигнал отладчику. Отладчик перерабатывает опкод и возвращает контроль над процессом виртуальной машине.
Трансляция опкодов производилась за счет использования таблицы джампов, содержащей адреса подпрограмм, каждая из которых отвечает за интерпретацию каждого возможного опкода(некое подобие таблицы импорта).
Всего существует 256 основных опкодов а также огромное количество опкодов, образованных с помощью префиксов. Каждый префикс может образовать ещё 256 опкодов и так до бесконечности.
Необходимость в инжектировании кода отпала, так как был создан лоадер, который загружал программу на VB в приостановленном режиме, получая точку входа в программу функцией GetThreadContext и копировал в точку входа код, который, в свою очередь загружал DLL отладчика. Когда патч выполнялся, он отмечал инициацию главного процесса, используя API SetEvent и WaitForSingleObject. Как только все закончено, патч откатывался и выполнение программы продолжалось с оригинальной точки входа с помощью API SetThreadContext.
2. Практический пример
Рассмотрим отладку псевдокода на примере Crackme#1 от Buzz'mFrog.
Интересно, что солюшена для этого крякмиса я пока в Инете не нашел. Пробовал ковырять VB Explorer'ом, но он почему-то вылетал; в итоге остановился на WKTVBDE и Ollydbg.
Крякмис требует ввести имя и правильный серийник.
Откроем его для начала в WKTVBDE. Здесь пока видна только главная процедура:
00402B38: F4 LitI2_Byte: -> 1h 1
00402B3A: 08 FLdPr
00402B3D: 8E MemStI2
00402B40: 13 ExitProcHresult; выход из процедуры
00402B41: 00 LargeBos
00402B43: 00 LargeBos
00402B45: 22 ImpAdLdPr
00402B48: 04 FLdRfVar 0012FAF4h

Для того, чтобы поставить брекпойнт на обработчик нажатия кнопки, воспользуемся кнопкой «Form manager», выберем на выпадающем меню «Form1», затем «Command» и там «command1». Напротив адреса обработчика есть кнопка «bpx», её и нажимаем. Далее достаточно запустить крякмис нажатием F5 и вбить туда своё имя и «предполагаемый» серийник. Брекпойнт срабатывает тут:
00402F80: 04 FLdRfVar 0012F3CCh
00402F83: 21 FLdPrThis 0015CC38h
00402F84: 0F VCallAd Form1.serialnr
00402F87: 19 FStAdFunc
00402F8A: 08 FLdPr
00402F8D: 0D VCallHresult get__ipropTEXTEDIT
00402F92: 6C ILdRf 00000000h
00402F95: 0A ImpAdCallFPR4 rtcR8ValFromBstr on address 7415D1FEh
00402F9A: 08 FLdPr
00402F9D: 92 MemStFPR8
00402FA0: 2F FFree1Str
00402FA3: 1A FFree1Ad
00402FA6: 04 FLdRfVar 0012F3CCh
00402FA9: 21 FLdPrThis 0015CC38h
00402FAA: 0F VCallAd Form1.username
00402FAD: 19 FStAdFunc
00402FB0: 08 FLdPr
00402FB3: 0D VCallHresult get__ipropTEXTEDIT
00402FB8: 6C ILdRf 00000000h

Трассируем поF8. Здесь начинается какая-то процедура:
00402E68: 10 ThisVCallHresult 00402CF0->00402CA8
...
00402E8C: 28 LitVarI2 0012F1D4h 1h, 1
00402E91: 04 FLdRfVar 0012F224h
00402E94: FC Lead1/CI4Var
00402E96: 6C ILdRf 0015CC6Ch
00402E99: 4D CVarRef:
00402E9E: 04 FLdRfVar 0012F1C4h
00402EA1: 0A ImpAdCallFPR4 rtcMidCharVar on address 740C306Eh; извлечение символов из имени по
00402EA6: 04 FLdRfVar 0012F1C4h; одному
00402EA9: FC Lead1/FStVar
00402EAD: 35 FFree1Var
00402EB0: 04 FLdRfVar 0012F1B4h
00402EB3: FD Lead2/CStrVarVal
00402EB7: 0B ImpAdCallI2 rtcAnsiValueBstr on address 740BC89Bh; здесь в стек
00402EBC: 44 CvarI2; заносятся символы имени по одному
00402EBF: FC Lead1/FStVar
00402EC3: 2F FFree1Str
00402EC6: 04 FLdRfVar 0012F244h
00402EC9: 04 FLdRfVar 0012F1A0h
00402ECC: 94 AddVar; тут что-то складывается
00402ED0: FC Lead1/FStVar
00402ED4: 04 FLdRfVar 0012F224h
00402ED7: FE Lead3/NextStepVar; это явно цикл
00402EDD: 28 LitVarI2 3E8h, 1000
00402EE2: 04 FLdRfVar 0012F244h
00402EE5: B4 MulVar; тут что-то перемножается
00402EE9: 04 FLdRfVar 0012F244h
00402EEC: 80 ILdI4
00402EEF: 4A FnLenStr; получают длину строки
00402EF0: FD Lead2/CVarI4
00402EF4: 9C SubVar; вычитание
00402EF8: 94 AddVar; сложение
00402EFC: FC Lead1/FStVar
00402F00: 04 FLdRfVar 0012F244h
00402F03: 08 FLdPr 0015CB10h
00402F06: 89 MemLdI2
00402F09: 44 CVarI2
00402F0C: B4 MulVar; умножение
00402F10: FC Lead1/FStVar
00402F14: 04 FLdRfVar 0012F244h
00402F17: E9 FnIntVar; конвертация в integer
00402F1B: 42 CR4Var
00402F1C: 74 FStFPR8
00402F1F: FF Lead4/ExitProcCbHresult; переход в основную ветвь программы

00402FD6: 6F FLdFPR8
00402FD9: 08 FLdPr
00402FDC: 92 MemStFPR8
00402FDF: 08 FLdPr
00402FE2: 8D MemLdFPR8
00402FE5: 08 FLdPr
00402FE8: 8D MemLdFPR8
00402FEB: C8 EqR4
00402FEC: 1C BranchF 00403089 (Jump »); условный переход по адресу 403089 на сообщение
00402FEF: 27 LitVar_Missing 0012F344h; о неправильном серийнике
00402FF2: 27 LitVar_Missing 0012F364h
00402FF5: 3A LitVarStr 'Very good!'
00402FFA: 4E FStVarCopyObj 0012F384h
00402FFD: 04 FLdRfVar 0012F384h
00403000: F5 LitI4: -> 0h 0
00403005: 3A LitVarStr 'Great Work! The serial is correct.'
0040300A: 4E FStVarCopyObj 0012F3A4h
0040300D: 04 FLdRfVar 0012F3A4h

Итак, что мы имеем:
Подпрограмма, отвечающая за генерацию ключа, находится в диапазоне адресов с 402e68 по 402f1f.
Примерная схема генерации такова:
1)символы имени считываются по одному и конвертируются в hex
2)происходит сложение ( пока неизвестно, чего именно)
3)после окончания цикла умножение
4)определение длины какой-то строки
5)вычитание
6)сложение
7)опять умножение
8)конвертация в Integer
Обладай я хоть какими-то знаниями по VB, всё было бы наверное легче, но пришлось помучиться. Увы, ни WKTVBDE, ни VB decompiler( во всяком случае его облегченная версия) здесь уже не помощники, и придется пускать в ход Ollydbg. Все мы знаем, что трассировать p-code этим отладчиком невозможно, поэтому применим один трюк. Как я уже говорил в начале, каждый опкод бейсика интерпретируется вирт. машиной, следовательно нужно поставить брекпойнт на чтение диапазона памяти, содержащего соответствующие опкоды, и дальше уже трассировать виртуальную машину. Итак, процедура генерации серийника начинается по адресу 402E68. Откроем крякмис в Ollydbg и поставим брекпойнт на доступ к памяти по этому адресу. Запускаем программу на выполнение, вводим имя и серийник, и сразу же оказываемся внутри виртуальной машины (я ввел имя Satyr, серийник 487201244) по адресу 7417D31C( адрес может отличаться). Сейчас необходимо выловить тот момент, когда происходит конвертация символов имени из Ascii в hex. Для Satyr это будет такая последовательность: (53 61 74 79 72). Далее трассируем программу, пока в глаза не бросается такая команда:
740BC8D3 66:0FB645 FE MOVZX AX,BYTE PTR SS:[EBP-2]
после выполнения которой в ax оказывается число 53h, первое из нашей последовательности. Поставим брекпойнт на следующую команду и попробуем трассировать код дальше, чтобы узнать, что происходит с кодами символов.
7417D79A 66:8948 08 MOV WORD PTR DS:[EAX+8],CX; вот тут код символа заносится в память
7417E667 894B 08 MOV DWORD PTR DS:[EBX+8],ECX; и здесь тоже

74187B00 0FBF46 08 MOVSX EAX,WORD PTR DS:[ESI+8]; тут к числу 53h прибавляется единица
74187B04 0FBF49 08 MOVSX ECX,WORD PTR DS:[ECX+8]
74187B08 03C8 ADD ECX,EAX

Потом ничего интересного не происходит, и после продолжительного трассирования мы останавливаемся на нашем брекпойнте по адресу 740bc8d8. Видим, что загружается код следующего символа (61h). Попробуем опять немного трассировать. Мы видим, что на том же адресе, на котором происходило прибавление единицы к 53h, происходит теперь сложение (54h+61h). Поставим брекпойнт по адресу 74187b0a. Теперь, каждый раз нажимая f9, можно отчетливо видеть, что происходит сложение кодов всех символов между собой. В результате мы получаем 53h+1h+61h+74h+79h+72h=214h.
Дальше всё очень просто. Можно трассировать код для большей уверенности, но проще поставить брекпойнт на чтение области памяти, в которой сохранено число 214h. вначале оно сохраняется в 12f1e8, а затем в 12f258. Когда брекпойнт срабатывает второй раз, мы оказываемся здесь:
741819FD 0FBF43 08 MOVSX EAX,WORD PTR DS:[EBX+8]
74181A01 0FAFC8 IMUL ECX,EAX

Эта конструкция перемножает константу 3e8h и наше число 214h, что в результате даёт 81e20h.
Заметим, что во время трассировки бросается в глаза наличие большого числа очень похожих циклов и структур. Это « изобретение» MS и подробнее об этих заглушках и структурах можно прочитать в статье GPcH « Декомпилируем p-code в уме».
Сейчас кое-что проясняется, а именно: прога придерживается именно той схемы, которая была определена в начале исследования. Мы уже прошли пункты 1, 2, и 3, теперь должно быть вычисление длины строки.
Вот этот код:
7417DBFA 8B4405 00 MOV EAX,DWORD PTR SS:[EBP+EAX]
В-общем-то, он находится в контексте функции vbaLenBstr, которая вычисляет длину строки, и понятно, что мы получим теперь число 5h.
Трассируем дальше:
74188AED 0FBF76 08 MOVSX ESI,WORD PTR DS:[ESI+8]; esi=214h
...
74182562 8B4F 08 MOV ECX,DWORD PTR DS:[EDI+8] ;ecx=5h
74182565 8BD6 MOV EDX,ESI ;edx=esi
74182567 8BF9 MOV EDI,ECX
74182569 2BD1 SUB EDX,ECX ;edx=214h-5h=20fh
Следующий шаг:
74180F6C 8B79 08 MOV EDI,DWORD PTR DS:[ECX+8] ;edi = 81e20h
74180F6F 8B4E 08 MOV ECX,DWORD PTR DS:[ESI+8] ;ecx = 20fh
...
74180F44 8D140F LEA EDX,DWORD PTR DS:[EDI+ECX] ;edx= 81e20h+20fh=8202fh

Далее идет небольшая проверка разрядности числа. Оно умножается на единицу и та часть, которая осталась в eax, принимает участие в дальнейших операциях.
740BE64C F76D 0C IMUL DWORD PTR SS:[EBP+C]
Затем число просто- напросто конвертируется в десятичную систему счисления. Здесь используется FPU:
7409B95F DB42 08 FILD DWORD PTR DS:[EDX+8]; st(0) = 532527,0000000000
В дальнейшем это число переводится из дробного формата в целый и сравнивается с введённым серийником. Т.е. для Satyr правильный серийник будет 532527.
Итак, алгоритм генерации серийника таков:
1)Имя переводится из ascii в hex (Satyr) = (53h 61h 74h 79h 72h)
2)Производится суммирование (53h + 1h + 61h + 74h + 79h + 72h) = 214h
3)Сумма умножается на 3e8h (214h*3e8h)=81e20h
4)Из той же суммы вычитается длина имени (214h-5h) = 20fh
5)Последнее число прибавляется к числу из п.3 (20fh+81e20h) = 8202fh
6)Проверяется разрядность полученного числа
7)Число переводится из hex в dec
Вот и всё. При знаниии алгоритма написание кейгена является делом техники. Исходник кейгена естественно прилагаю.
Все файлы, использованные в данном примере, находятся пока здесь
slil.ru/24933301
(крякмис, кейген и исходник кейгена).
Нравится
Не нравится

1 комментарий

14:20
Прошу прощения, забыл приложить крякмис. Теперь все файлы находятся здесь : http://hackroot.ucoz.ru/load/0-0-0-1-20