160 个CrackMe之 124 - noos.3 (VB5 p-code)算法分析和5秒爆破算码注册机
本帖最后由 solly 于 2019-6-23 22:31 编辑160 个 CrackMe 之 124 -- noos.3 是一个VB5编写的CrackMe程序,运行时需要 Visual Basic 5.0 运行库支持(MSVBVM50.DLL等),同时这个程序是编译成p-code的程序,需要跟踪p-code中间码,虽然我从QB4.5,VB4到VB6都用过,后面的VB.NET没用过,对Basic语法非常了解(show years old?),但对p-code没怎么了解过,以前也没有跟踪过,所以跟踪这个CM花了不少时间,了解了p-code的跟踪,虽然VB p-code现在用不到了,但我们的目标不是在于用不用这个语言,而是在于理解更多的知识,老知识是新知识的基础,就象现在的虚拟机加壳,是不是与p-code码有点相似???
这个CM的追踪,用到了几个工具,包括 WKTVBDebugger,VBExplorer和OllyICE,三个工具各有各的作用。
1、WKTVBDebugger不是用来调试和跟踪,而是用来查询MSVBVM50.DLL中对p-code的中间码进行解释执行的各个函数。
2、VBExplorer用来反编译CM程序,找到各个控件的执行代码,不过还是p-code代码。(试过其它几种VB反编译器,这个CM用VBExplorer效果完美,其它几个都不行,包括 VB Decompiler Pro都不行)
3、OllyICE则是用来一步一步跟踪调试程序,当然跟踪的都是 MSVBVM50.DLL 中的代码,因为 VB p-code代码都是由MSVBVM50.DLL来执行,没有原生二进制代码。至于为什么用OllyICE,而不用 WKTVBDebugger,那就是WKTVBDebugger 是 p-code 代码级跟踪,但一个p-code包括多个微操作,只有 OllyICE跟踪MSVBVM50.DLL才看得到,而 WKTVBDebugger 却不行,特别是一些数据保存,用WKTVBDebugger跟踪,不晓得存到哪里去了,还有那个栈的显示方式也不方便。
另外,为了后期算法效果验证,装了一个win98的虚拟机和一套Visual Basic 5,把p-code代码改编成 basic代码,直接将这个 CM reverse成一个basic源码CM了。
VB p-code 代码中,有下面几个特点:
1、MSVBVM50.DLL 将CPU的寄存器 ESI 是作为p-code指令寄存器使用的,MSVBVM50.DLL内部通过ESI中的地址来读取指令,并解释执行。
2、使用 EBP 寄存器作为变量的地址基址寄存器,所有变量在 p-code 的指令中都是保存为一个相对偏移量,在访问变量前需加上EBP的值,作为其真正的数据地址来读取数据。
3、函数调用的参数、参与计算的变量的值,或者字符串数据的地址,都是通过栈来管理的,数据使用前,均push到栈,指令执行时再pop出来,运算或调用的结果再重新push到栈,这个有点类似CPU的浮点指令的处理方式
4、每个p-code指令执行完成后,接着会通过ESI的指示取出下一条指令到EAX中,再通过一条类似 JMP DWORD PTR的指令跳转到该指令的解释程序,对刚取出的指令进行处理,依次类推。。。
VB中的变量,可以不定义类型,默认是一个 Variant 类型,这类种类型对于跟踪来説,也非常麻烦,占用16个字节不説,也不直观,数字还好,字符串还是一个引用地址,比较麻烦。
Variant中VS98的头文件中有定义,如下:
structtagVARIANT
{
union
{
struct__tagVARIANT
{
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union
{
LONG lVal;
BYTE bVal;
SHORT iVal;
FLOAT fltVal;
DOUBLE dblVal;
VARIANT_BOOL boolVal;
_VARIANT_BOOL bool;
SCODE scode;
CY cyVal;
DATE date;
BSTR bstrVal;
IUnknown __RPC_FAR *punkVal;
IDispatch __RPC_FAR *pdispVal;
SAFEARRAY __RPC_FAR *parray;
BYTE __RPC_FAR *pbVal;
SHORT __RPC_FAR *piVal;
LONG __RPC_FAR *plVal;
FLOAT __RPC_FAR *pfltVal;
DOUBLE __RPC_FAR *pdblVal;
VARIANT_BOOL __RPC_FAR *pboolVal;
_VARIANT_BOOL __RPC_FAR *pbool;
SCODE __RPC_FAR *pscode;
CY __RPC_FAR *pcyVal;
DATE __RPC_FAR *pdate;
BSTR __RPC_FAR *pbstrVal;
IUnknown __RPC_FAR *__RPC_FAR *ppunkVal;
IDispatch __RPC_FAR *__RPC_FAR *ppdispVal;
SAFEARRAY __RPC_FAR *__RPC_FAR *pparray;
VARIANT __RPC_FAR *pvarVal;
PVOID byref;
CHAR cVal;
USHORT uiVal;
ULONG ulVal;
INT intVal;
UINT uintVal;
DECIMAL __RPC_FAR *pdecVal;
CHAR __RPC_FAR *pcVal;
USHORT __RPC_FAR *puiVal;
ULONG __RPC_FAR *pulVal;
INT __RPC_FAR *pintVal;
UINT __RPC_FAR *puintVal;
struct__tagBRECORD
{
PVOID pvRecord;
IRecordInfo __RPC_FAR *pRecInfo;
} __VARIANT_NAME_4;
} __VARIANT_NAME_3;
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
};
其中的成员 VARTYPE vt; 指明当前 Variable 变量中保存的实际数据类型,有以下取值:
enum VARENUM{
VT_EMPTY = 0,
VT_NULL = 1,
VT_I2 = 2,
VT_I4 = 3,
VT_R4 = 4,
VT_R8 = 5,
VT_CY = 6,
VT_DATE = 7,
VT_BSTR = 8,
VT_DISPATCH = 9,
VT_ERROR = 10,
VT_BOOL = 11,
VT_VARIANT= 12,
VT_UNKNOWN= 13,
VT_DECIMAL= 14,
VT_I1 = 16,
VT_UI1 = 17,
VT_UI2 = 18,
VT_UI4 = 19,
VT_I8 = 20,
VT_UI8 = 21,
VT_INT = 22,
VT_UINT = 23,
VT_VOID = 24,
VT_HRESULT= 25,
VT_PTR = 26,
VT_SAFEARRAY = 27,
VT_CARRAY = 28,
VT_USERDEFINED = 29,
VT_LPSTR = 30,
VT_LPWSTR = 31,
VT_RECORD = 36,
VT_INT_PTR = 37,
VT_UINT_PTR = 38,
VT_FILETIME = 64,
VT_BLOB = 65,
VT_STREAM = 66,
VT_STORAGE = 67,
VT_STREAMED_OBJECT= 68,
VT_STORED_OBJECT = 69,
VT_BLOB_OBJECT = 70,
VT_CF = 71,
VT_CLSID = 72,
VT_VERSIONED_STREAM = 73,
VT_BSTR_BLOB = 0x0fff,
VT_VECTOR = 0x1000,
VT_ARRAY = 0x2000,
VT_BYREF = 0x4000,
VT_RESERVED = 0x8000,
VT_ILLEGAL = 0xffff,
VT_ILLEGALMASKED = 0x0fff,
VT_TYPEMASK = 0x0fff
} ;
更详细的了解可去 https://docs.microsoft.com/en-us ... p/variant-data-type 看看。
现在进入正题。
下面将过程简单讲一下,先运行一下CM,看看界面:
该CM是要求输入一个Unlock码来解锁,将界面上的红灯变成绿灯,同时将上面那个锁的图片由“Activated”变成“Deactivated”。
该CM的算法相对简单,先生成一个数组,并初始化一些计算后的数据,一些固定值,但CM为了增加难度,是动态运算生成的数据。并且还会生成另外一个索引数组,CM居然一个一个的数组元素进行赋值,一共62个元素,进行了62次同样的操作,导到p-code代码好长。不过,所有的算法都是在一个方法内,没有多层次的调用。
要跟踪p-code代码,先要定位解释p-code代码执行的代码在 MSVBVM50.DLL 中的位置,这个就是通过 WKTVBDebugger 来完成的,下图是 WKTVBDebugger 界面(在我win10机器上,要运行loader两次才会弹出这个调试界面):
先点击“Form Manager (Ctrl+F)”按钮,调出 VB Form资源管理界面,见上图的左边,在最上面的下拉框中选择“Main",下面几个按钮就可以用了,再点击”command“按钮,在弹出的面板中的下拉框中,选择”Command1“,就显示出了VB程序command按钮的相关信息,如下图:
我们再将上图中的”BPX“按钮按下,这样就可以断下Command1的Click()事件。
再次我们点击WTKVBDebugger主界面中的”Opcodes (Ctrl+O)“按钮,就会弹出一个”Opcodes Control dialog“,这里就是各种p-code指令在MSVBVM50.DLL中解释执行的子例程地址了,如下图所示:
如 p-code指令LitStr:的p-code码为0x1B,由msvbvm50.dll中地址为0x741BDE59开始的代码来解释执行的。通过这个地址,我们就可以在 OllyICE中下条件断点了,条件就是ESI的值,这个值在哪里找呢,可以在 WKTVBDebugger中找到,也可以通过VBExplorer来找到,用WKTVBDebugger不方便,我是用VBExplorer来找这个值的,如下图:
在VBExplorer中,很方便就可以定位各个控件的事件处理过程,在VB中叫做Sub(),可以在代码区看到,指令区第一列就是地址,如上图中的":0041EA08",这个值就是条件断点中ESI的值。
VB还可以查看Form中各个控件的参数,如下图,在列表中可以选择各个控件,查看相关参数:
以上是一些基础知识,下面进入 OllyICE 操作,启动OllyICE,并载入noos.3.exe程序:
载入后,只有两行指令,就是进入MSVBVM50.DLL。右击”转到-->表达式”,进入下图:
我们在VBExplorer中查到Command1.Click()事件中的第一条指令是“LargeBos”,然后通过 WKTVBDebugger中的Opcodes Control Dialog查得"LargeBos"的解释代码位于MSVBVM50.DLL中地址0x741BD39E处,我们就在OllyICE中输入这个地址,点“确定”,来到了MSVBVM50.DLL的代码空间:
然后就在这里下断点,就可以断下Command1.Click()事件,不过要设置条件,如下图,下一个条件断点:
条件就是p-code的ESI指令寄存器的值,这个值就是 VBExplorer中的指令地址值,因此条件是 ESI>= 0x0041EA08,这里不要用==,要用>=,因为p-code执行前会先取指,当执行这个指令时esi一般指向了指令的参数或下一个指令了,如下图:
下完条件断点后,地址区会显示紫色背景,如下图:
到这里,就可以进行跟踪执行了,在OllyICE中跟踪执行p-code代码时,要时刻对照VBExplorer中的指令,通过OllyICE中的ESI和VBExplorer中的地址值,就可以确定当前正在执行哪条p-code指令,这样就能大概确定当前代码正在干什么,不然就是在MSVBVM中乱转了。
下面是跟踪本CM下的所有断点,可以参考一下:
下面説一下,VB的Variant类型变量在内存中的形式,如下图:
这是一个16字节的Variant变量,保存了字符串"z"。最前面黄色框框中0x0008表示变量保存的是字符串,后面黄色框框中保存的是“z”字符串的地址,表示"z"保存在“0x006116A4"处,如下图:
如果是数字类变量,则保存的直接就是数字,不是地址了。
以上是一些操作手法説明,下面对VB代码进一步説明,具体的每个p-code代码的跟踪就不説了,那是一个体力活。
该CM的算法写在一个子函数中(在VBExplorer中可找到),Command1.Click()事件会调用这个子函数,下面对函数作简要分析:
:0041F468F500000000 LitI4 ;Push 00000000
:0041F46DF53D000000 LitI4 ;Push 0000003D
:0041F4720418FF FLdRfVar ;Push LOCAL_00E8
:0041F475FE8E0100FFFF1000 Redim ;
******Possible String Ref To->"0"
|
:0041F47F3A08FF1100 LitVarStr ;PushVarString ptr_0041DDE0
:0041F484F500000000 LitI4 ;Push 00000000
:0041F4896C18FF ILdRf ;Push DWORD
:0041F48CFCB0 Ary1StVarCopy ;
******Possible String Ref To->"1"
|
:0041F48E3AF8FE1200 LitVarStr ;PushVarString ptr_0041DDE8
:0041F493F501000000 LitI4 ;Push 00000001
:0041F4986C18FF ILdRf ;Push DWORD
:0041F49BFCB0 Ary1StVarCopy ;
'
'
'此处省略 n 行 p-code代码,这大段代码(包括前后留下来的部分代码)就是给一个62个元素的数组赋值
'
'
:0041F801FCB0 Ary1StVarCopy ;
******Possible String Ref To->"y"
|
:0041F8033A48FB4D00 LitVarStr ;PushVarString ptr_0041DFA0
:0041F808F53C000000 LitI4 ;Push 0000003C
:0041F80D6C18FF ILdRf ;Push DWORD
:0041F810FCB0 Ary1StVarCopy ;
******Possible String Ref To->"z"
|
:0041F8123A38FB4E00 LitVarStr ;PushVarString ptr_0041DFA8
:0041F817F53D000000 LitI4 ;Push 0000003D
:0041F81C6C18FF ILdRf ;Push DWORD
:0041F81FFCB0 Ary1StVarCopy ;
'
'上面代码生成一个含62个元素的数组,每个元素保存一个字符,字符如下,我们用字符串代替:
' Dim charString As String
' charString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
'这个数组就是后面用来进行对照我们输入的unlock码进行查表的
'
:0041F8210418FF FLdRfVar ;Push LOCAL_00E8
:0041F8240428FB FLdRfVar ;Push LOCAL_04D8
**********Reference To->msvbvm50.rtcArray
|
:0041F8270A4F000800 ImpAdCallFPR4 ;Call ptr_0040100C; check stack 0008; Push EAX
:0041F82C0418FF FLdRfVar ;Push LOCAL_00E8
:0041F82F5A Erase ;vbaErase
:0041F8300428FB FLdRfVar ;Push LOCAL_04D8
:0041F833FCF668FF FStVar ;
******Possible String Ref To->""
|
:0041F8371B0500 LitStr ;Push ptr_0041DD34
:0041F83A4364FF FStStrCopy ;=SysAllocStringByteLen(Pop, ); SysFreeString Pop
'
'以上代码是复制前面生成的数组,并清理临时变量的空间
'我们将这个数组定义为:
'Dim charArray(62) As Variant 'LOCAL_00E8
'
:0041F83DF518000000 LitI4 ;Push 00000018
:0041F8427154FF FStR4 ;Pop DWORD
:0041F845F51C000000 LitI4 ;Push 0000001C
:0041F84A7158FF FStR4 ;Pop DWORD
'
'以上代码生成两个变量并初始化,我们定义如下,并初始化:
'Dim baseA As Long 'LOCAL_00A8
'Dim baseB As Long 'LOCAL_00AC
'baseA = 28 'H1C
'baseB = 24 'H18
' 下面两个是其它变量定义,后面要用到,这里没相应代码,也没有初始化
'Dim i As Long, j As Long
'Dim f As Double
'--------------------------------------------------------------------------
'下面是定义另一个数组,也是62个元素,并且初始化,我们定义如下:
' Dim Dim charArray2(62) As Variant ' LOCAL_00CC
'
'以下代码初始化前10个元素:
:0041F84DF500000000 LitI4 ;Push 00000000
:0041F852045CFF FLdRfVar ;Push LOCAL_00A4
:0041F855F509000000 LitI4 ;Push 00000009
:0041F85AFE6420FB3104 ForI4 ;
'上面是一个For循环定义
' for i=0 to 9 step 1
'
:0041F8606C5CFF ILdRf ;Push DWORD
:0041F8636C5CFF ILdRf ;Push DWORD
:0041F866B2 MulI4 ;
:0041F8676C5CFF ILdRf ;Push DWORD
:0041F86AB2 MulI4 ;
:0041F86BFD6908FF CVarI4 ;
:0041F86F6C5CFF ILdRf ;Push DWORD
:0041F8720434FF FLdRfVar ;Push LOCAL_00CC
:0041F87552 Ary1StVar ;
:0041F8766C5CFF ILdRf ;Push DWORD
:0041F8790434FF FLdRfVar ;Push LOCAL_00CC
:0041F87CFC96 Ary1LdRfVar ;
:0041F87E2808FF1700 LitVarI2 ;PushVarInteger 0017
:0041F883FB9428FB AddVar ;
:0041F8876C5CFF ILdRf ;Push DWORD
:0041F88A0434FF FLdRfVar ;Push LOCAL_00CC
:0041F88D52 Ary1StVar ;
:0041F88E3528FB FFree1Var ;Free LOCAL_04D8
'以上是循环体,生成第二个数组的前10个元素,对应VB代码如下,是不是很简单:
' charArray2(i) = i * i * i + 23
'
:0041F891045CFF FLdRfVar ;Push LOCAL_00A4
:0041F8946620FBF803 NextI4 ;
'循环结束
'Next i
''以下代码初始化中间26个元素:
:0041F899F50A000000 LitI4 ;Push 0000000A
:0041F89E045CFF FLdRfVar ;Push LOCAL_00A4
:0041F8A1F523000000 LitI4 ;Push 00000023
:0041F8A6FE6418FB8E04 ForI4 ;
'上面是一个For循环定义
' for i=10 to 35 step 1
'
:0041F8AC6C58FF ILdRf ;Push DWORD
:0041F8AFF502000000 LitI4 ;Push 00000002
:0041F8B4B2 MulI4 ;
:0041F8B57158FF FStR4 ;Pop DWORD
:0041F8B86C58FF ILdRf ;Push DWORD
******Possible String Ref To->"MTV"
|
:0041F8BB1B5000 LitStr ;Push ptr_0041DFB0
**********Reference To->msvbvm50.rtcAnsiValueBstr
|
:0041F8BE0B51000400 ImpAdCallI2 ;Call ptr_00401012; check stack 0004; Push EAX
:0041F8C3E7 CI4UI1 ;
:0041F8C4FB13 XorI4 ;
:0041F8C6EC CR8I4 ;
:0041F8C7744CFF FStFPR8 ;Fstp#8
:0041F8CA6F4CFF FLdFPR8 ;Fld#8
:0041F8CDF405 LitI2_Byte ;Push 05
:0041F8CFEB CR8I2 ;
:0041F8D0AB AddR8 ;
:0041F8D1744CFF FStFPR8 ;Fstp#8
:0041F8D46F4CFF FLdFPR8 ;Fld#8
:0041F8D7FD6B08FF CVarR8 ;
:0041F8DB6C5CFF ILdRf ;Push DWORD
:0041F8DE0434FF FLdRfVar ;Push LOCAL_00CC
:0041F8E152 Ary1StVar ;
:0041F8E26C58FF ILdRf ;Push DWORD
:0041F8E5F501000000 LitI4 ;Push 00000001
:0041F8EAAA AddI4 ;
:0041F8EB7158FF FStR4 ;Pop DWORD
'以上是循环体,生成第二个数组的中间26个元素,对应VB代码如下:
' baseA = baseA * 2
' f = (baseA Xor Asc("MTV")) + 5
' charArray2(i) = f
' baseA = baseA + 1
'
:0041F8EE045CFF FLdRfVar ;Push LOCAL_00A4
:0041F8F16618FB4404 NextI4 ;
'循环结束
'Next i
''下面是初始化第二个数组的最后26个元素:
:0041F8F6F524000000 LitI4 ;Push 00000024
:0041F8FB045CFF FLdRfVar ;Push LOCAL_00A4
:0041F8FEF53D000000 LitI4 ;Push 0000003D
:0041F903FE6410FBEB04 ForI4 ;
'上面是For循环定义
' For i=36 to 61 step 1
'
:0041F9096C54FF ILdRf ;Push DWORD
:0041F90CF502000000 LitI4 ;Push 00000002
:0041F911B2 MulI4 ;
:0041F9127154FF FStR4 ;Pop DWORD
:0041F9156C54FF ILdRf ;Push DWORD
******Possible String Ref To->"TMF"
|
:0041F9181B5200 LitStr ;Push ptr_0041DFBC
**********Reference To->msvbvm50.rtcAnsiValueBstr
|
:0041F91B0B51000400 ImpAdCallI2 ;Call ptr_00401012; check stack 0004; Push EAX
:0041F920E7 CI4UI1 ;
:0041F921FB13 XorI4 ;
:0041F923EC CR8I4 ;
:0041F924744CFF FStFPR8 ;Fstp#8
:0041F9276F4CFF FLdFPR8 ;Fld#8
:0041F92AF43E LitI2_Byte ;Push 3E
:0041F92CEB CR8I2 ;
:0041F92DAB AddR8 ;
:0041F92E744CFF FStFPR8 ;Fstp#8
:0041F9316F4CFF FLdFPR8 ;Fld#8
:0041F934FD6B08FF CVarR8 ;
:0041F9386C5CFF ILdRf ;Push DWORD
:0041F93B0434FF FLdRfVar ;Push LOCAL_00CC
:0041F93E52 Ary1StVar ;
:0041F93F6C54FF ILdRf ;Push DWORD
:0041F942F501000000 LitI4 ;Push 00000001
:0041F947AA AddI4 ;
:0041F9487154FF FStR4 ;Pop DWORD
'以上是循环体,生成第二个数组的中间26个元素,对应VB代码如下:
' baseB = baseB * 2
' f = (baseB Xor Asc("TMF")) + 62
' charArray2(i) = f
' baseB = baseB + 1
'
:0041F94B045CFF FLdRfVar ;Push LOCAL_00A4
:0041F94E6610FBA104 NextI4 ;
' 循环结束
'Next i
''初始化结束,下面就是校验unlock码的过程
:0041F9530408FB FLdRfVar ;Push LOCAL_04F8
:0041F956050100 ImpAdLdRf ;Push ptr
:0041F959240200 NewIfNullPr ;
:0041F95C0F1C03 VCallAd ;Return the control index 09
:0041F95F190CFB FStAdFunc ;
:0041F962080CFB FLdPr ;=
***********Reference To:TextBox.Text
|
:0041F9650DA0000600 VCallHresult ;Call ptr_0041DD38
'以上代码,是取界面文本框中的 unlock code
' Dim sn As String
' sn = txtCode.Text
'
:0041F96A6C08FB ILdRf ;Push DWORD
:0041F96D4A FnLenStr ;vbaLenBstr
'以上代码计算unlock code 的长度
'Dim snlen As Integer
'Dim snlen2 As Integer
'
'snlen = Len(sn)
'
:0041F96EF501000000 LitI4 ;Push 00000001
:0041F973AA AddI4 ;
:0041F974FC0E CUI1I4 ;
:0041F976FCF062FF FStUI1 ;
:0041F97A2F08FB FFree1Str ;SysFreeString ; =0
:0041F97D1A0CFB FFree1Ad ;Push ; Call [[]+8]; []=0
:0041F980F401 LitI2_Byte ;Push 01, 循环起点 i=1
:0041F982FC0D CUI1I2 ;
:0041F9840460FF FLdRfVar ;Push LOCAL_00A0, 循环变量 i
:0041F987FCE062FF FLdUI1 ;
:0041F98BFC14 CI2UI1 ;No Operation
:0041F98DF401 LitI2_Byte ;Push 01
:0041F98FAD SubI2 ;
:0041F990FC0D CUI1I2 ;, 循环结束值 = (snlen2-1)
:0041F992FE6204FB8005 ForUI1 ;
'上面生成另一个长度值加上1的变量,作为后面循环的结束值:
' snlen2 = (snlen + 1)
'同时生成一个循环
' Dim snArray(22) As String
' unlock code 最长为20字符,界面上限定了,最多输入20个字符,因此定义 snArray(22) 用于保存unlock code
' For j=1 to (snlen2-1) step 1
'-----------------------------------------------------------------------
' 下面是一个循环体将unlock code 分解成单个字符并放入数组snArray(22)
:0041F9980408FB FLdRfVar ;Push LOCAL_04F8
:0041F99B050100 ImpAdLdRf ;Push ptr
:0041F99E240200 NewIfNullPr ;
:0041F9A10F1C03 VCallAd ;Return the control index 09
:0041F9A4190CFB FStAdFunc ;
:0041F9A7080CFB FLdPr ;=
***********Reference To:TextBox.Text
|
:0041F9AA0DA0000600 VCallHresult ;Call ptr_0041DD38
:0041F9AF28F4FA0100 LitVarI2 ;PushVarInteger 0001
:0041F9B4FCE060FF FLdUI1 ;
:0041F9B8E7 CI4UI1 ;
:0041F9B93E08FB FLdZeroAd ;Push DWORD ; =0
:0041F9BC4628FB CVarStr ;
:0041F9BF04E4FA FLdRfVar ;Push LOCAL_051C
**********Reference To->msvbvm50.rtcMidCharVar
|
:0041F9C20A53001000 ImpAdCallFPR4 ;Call ptr_00401018; check stack 0010; Push EAX
:0041F9C704E4FA FLdRfVar ;Push LOCAL_051C
:0041F9CAFCE060FF FLdUI1 ;
:0041F9CEE7 CI4UI1 ;
:0041F9CF041CFF FLdRfVar ;Push LOCAL_00E4
:0041F9D252 Ary1StVar ;
:0041F9D31A0CFB FFree1Ad ;Push ; Call [[]+8]; []=0
:0041F9D636060028FBF4FAE4 FFreeVar ;Free 0006/2 variants
'以上是循环体,完成以下功能,就是将unlock code 分解成字符存入数组
'snArray(j) = Mid(sn, j, 1)
'
:0041F9DF0460FF FLdRfVar ;Push LOCAL_00A0
:0041F9E2FE7804FB3005 NextUI1 ;
'循环结束
'Next j
'--------------------------------------------------------------------------------------
'下面是对unlock code 数组进行运算校验是否合法,为两层循环,进行校验。
:0041F9E8F400 LitI2_Byte ;Push 00
:0041F9EAFBFD CStrUI1 ;vbaStrI2
:0041F9EC3178FF FStStr ;SysFreeString ; =Pop
'始化变量
'Dim s3 As String
's3 = CStr(0)
'Dim f3 As Double
'f3 = 0
'
:0041F9EFF501000000 LitI4 ;Push 00000001
:0041F9F40458FF FLdRfVar ;Push LOCAL_00A8
:0041F9F7FCE062FF FLdUI1 ;
:0041F9FBFC14 CI2UI1 ;No Operation
:0041F9FDF401 LitI2_Byte ;Push 01
:0041F9FFAD SubI2 ;
:0041FA00E7 CI4UI1 ;
:0041FA01FE64DCFA0A06 ForI4 ;
'第一层循环
' For i=1 to (snlen2-1) step 1
' 遍历 unlock code 数组
:0041FA07F500000000 LitI4 ;Push 00000000
:0041FA0C045CFF FLdRfVar ;Push LOCAL_00A4
:0041FA0FF53D000000 LitI4 ;Push 0000003D
:0041FA14FE64D4FA0206 ForI4 ;
'第二层循环
' For j=0 to 61 step 1
'遍历那个 0~z的字符串数组,找出与unlock code 中对应的字符的索引值,通过索引值查询第2个62元素数组中保存的相同索引的数据
:0041FA1A6C58FF ILdRf ;Push DWORD
:0041FA1D041CFF FLdRfVar ;Push LOCAL_00E4
:0041FA20FC96 Ary1LdRfVar ;
:0041FA22045CFF FLdRfVar ;Push LOCAL_00A4
:0041FA25FD930340 CDargRef ;
:0041FA290468FF FLdRfVar ;Push LOCAL_0098
:0041FA2CFEAE28FB0100 VarIndexLdVar ;
:0041FA32FB33 EqVarBool ;
:0041FA343528FB FFree1Var ;Free LOCAL_04D8
:0041FA371CFA05 BranchF ;If Pop=0 then ESI=0041FA62
:0041FA3A6C78FF ILdRf ;Push DWORD
:0041FA3D4608FF CVarStr ;
:0041FA406C5CFF ILdRf ;Push DWORD
:0041FA430434FF FLdRfVar ;Push LOCAL_00CC
:0041FA46FC96 Ary1LdRfVar ;
:0041FA48FB9428FB AddVar ;
:0041FA4C60 CStrVarTmp ;
:0041FA4D3178FF FStStr ;SysFreeString ; =Pop
:0041FA503528FB FFree1Var ;Free LOCAL_04D8
:0041FA536C78FF ILdRf ;Push DWORD
:0041FA56FC33 CR8Str ;
:0041FA586C58FF ILdRf ;Push DWORD
:0041FA5BEC CR8I4 ;
:0041FA5CB3 MulR8 ;
:0041FA5DFC00 CStrR8 ;
:0041FA5F3178FF FStStr ;SysFreeString ; =Pop
'循环体,完成以下运算过程:
' 'If snArray(i) = charArray(j) Then 'using array
' If snArray(i) = Mid(charString, j + 1, 1) Then 'using string
' f3 = Val(s3) + charArray2(j)
' s3 = CStr(f3 * i)
' End If
'
:0041FA62045CFF FLdRfVar ;Push LOCAL_00A4
:0041FA6566D4FAB205 NextI4 ;
'内层循环结束
'Next j
':0041FA6A0458FF FLdRfVar ;Push LOCAL_00A8
:0041FA6D66DCFA9F05 NextI4 ;
'外层循环结束
'Next i
':0041FA726C78FF ILdRf ;Push DWORD
:0041FA75FC33 CR8Str ;
:0041FA77FA407D1573D18401 LitDate ;
:0041FA80C8 EqR4 ;
:0041FA811CC806 BranchF ;If Pop=0 then ESI=0041FB30
'以上代码是对运行结果与一个常量进行对比,上面p-code代码显示是 Date类型,反正就是一个长整数"616388714737576#",VB代码如下:
' If Val(s3) != 616388714737576# Then
' goto 0041FB30
' End if
'
' 下面校验正确时的处理:
******Possible String Ref To->"Thank you for registering"
|
:0041FA841B0000 LitStr ;Push ptr_0041DCFC
:0041FA875408001000 FMemStStrCopy ;
:0041FA8C2828FB0100 LitVarI2 ;PushVarInteger 0001
:0041FA91F508000000 LitI4 ;Push 00000008
:0041FA960478FF FLdRfVar ;Push LOCAL_0088
:0041FA994D08FF0840 CVarRef ;
:0041FA9E04F4FA FLdRfVar ;Push LOCAL_050C
**********Reference To->msvbvm50.rtcMidCharVar
|
:0041FAA10A53001000 ImpAdCallFPR4 ;Call ptr_00401018; check stack 0010; Push EAX
:0041FAA604F4FA FLdRfVar ;Push LOCAL_050C
:0041FAA928E4FA0100 LitVarI2 ;PushVarInteger 0001
:0041FAAEF501000000 LitI4 ;Push 00000001
:0041FAB30478FF FLdRfVar ;Push LOCAL_0088
:0041FAB64DE8FE0840 CVarRef ;
:0041FABB04C4FA FLdRfVar ;Push LOCAL_053C
**********Reference To->msvbvm50.rtcMidCharVar
|
:0041FABE0A53001000 ImpAdCallFPR4 ;Call ptr_00401018; check stack 0010; Push EAX
:0041FAC304C4FA FLdRfVar ;Push LOCAL_053C
:0041FAC6FB94B4FA AddVar ;
:0041FACAFC22 CI4Var ;vbaI4Var
:0041FACC9908001400 FMemStI4 ;Pop DWORD [+0008]
:0041FAD1360A0028FBE4FAF4 FFreeVar ;Free 000A/2 variants
:0041FADE2828FB0100 LitVarI2 ;PushVarInteger 0001
:0041FAE3F50D000000 LitI4 ;Push 0000000D
:0041FAE80478FF FLdRfVar ;Push LOCAL_0088
:0041FAEB4D08FF0840 CVarRef ;
:0041FAF004F4FA FLdRfVar ;Push LOCAL_050C
**********Reference To->msvbvm50.rtcMidCharVar
|
:0041FAF30A53001000 ImpAdCallFPR4 ;Call ptr_00401018; check stack 0010; Push EAX
:0041FAF804F4FA FLdRfVar ;Push LOCAL_050C
:0041FAFB28E4FA0100 LitVarI2 ;PushVarInteger 0001
:0041FB00F505000000 LitI4 ;Push 00000005
:0041FB050478FF FLdRfVar ;Push LOCAL_0088
:0041FB084DE8FE0840 CVarRef ;
:0041FB0D04C4FA FLdRfVar ;Push LOCAL_053C
**********Reference To->msvbvm50.rtcMidCharVar
|
:0041FB100A53001000 ImpAdCallFPR4 ;Call ptr_00401018; check stack 0010; Push EAX
:0041FB1504C4FA FLdRfVar ;Push LOCAL_053C
:0041FB18FB94B4FA AddVar ;
:0041FB1CFC22 CI4Var ;vbaI4Var
:0041FB1E9908001800 FMemStI4 ;Pop DWORD [+0008]
:0041FB23360A0028FBE4FAF4 FFreeVar ;Free 000A/2 variants
'
'上面代码是生成三个变量:
'Dim thankyou As String
'Dim a1 As Integer, a2 As Integer
' thankyou = "Thank you for registering"
' a1 = Val(Mid(s3, 8, 1) + Mid(s3, 1, 1)) '16
' a2 = Val(Mid(s3, 13, 1) + Mid(s3, 5, 1)) '58
'这三个变量,是下面子函数调用的参数。
'其中一个字符串是"Thank you for registering",不过并不会通过对话框提示
'a1, a2 则是计算后的校验码约束:
' 1、计算后的校验码第8位为1
' 2、计算后的校验码第1位为6
' 3、计算后的校验码第13位为5
' 4、计算后的校验码第5位为8
'-----------------------------------------------------------------------------------------------
' 校验失败来到这里
' 调用一个了函数进行界面状态的显示(不管校验正确与否,都会调用下面的子函数)
**********Reference To-> sub_0041F430
|
:0041FB300A54000000 ImpAdCallFPR4 ;Call ptr_0041D60C; check stack 0000; Push EAX
:0041FB3514 ExitProc ;end proc
:0041FB360000 LargeBos ;IDE beginning of line with 00 byte codes
上面就是对关键算法的分析,用Visual Basic语法表示如下:
Public Sub SUB_0041FB38(ByRef iFlag%)
Dim str1 As String 'LOCAL_009C
Dim baseA As Long 'LOCAL_00A8
Dim baseB As Long 'LOCAL_00AC
' iFlag -->00A4
Dim charArray(62) As Variant 'LOCAL_00E8
Dim charArray2(62) As Variant ' LOCAL_00CC
Dim charString As String
On Error Resume Next
charString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
'using string replace of array follow:
charArray(0) = "0"
charArray(1) = "1"
charArray(2) = "2"
charArray(3) = "3"
charArray(4) = "4"
charArray(5) = "5"
charArray(6) = "6"
charArray(7) = "7"
charArray(8) = "8"
charArray(9) = "9"
charArray(10) = "A"
charArray(11) = "B"
charArray(12) = "C"
charArray(13) = "D"
charArray(14) = "E"
charArray(15) = "F"
charArray(16) = "G"
charArray(17) = "H"
charArray(18) = "I"
charArray(19) = "J"
charArray(20) = "K"
charArray(21) = "L"
charArray(22) = "M"
charArray(23) = "N"
charArray(24) = "O"
charArray(25) = "P"
charArray(26) = "Q"
charArray(27) = "R"
charArray(28) = "S"
charArray(29) = "T"
charArray(30) = "U"
charArray(31) = "V"
charArray(32) = "W"
charArray(33) = "X"
charArray(34) = "Y"
charArray(35) = "Z"
charArray(36) = "a"
charArray(37) = "b"
charArray(38) = "c"
charArray(39) = "d"
charArray(40) = "e"
charArray(41) = "f"
charArray(42) = "g"
charArray(43) = "h"
charArray(44) = "i"
charArray(45) = "j"
charArray(46) = "k"
charArray(47) = "l"
charArray(48) = "m"
charArray(49) = "n"
charArray(50) = "o"
charArray(51) = "p"
charArray(52) = "q"
charArray(53) = "r"
charArray(54) = "s"
charArray(55) = "t"
charArray(56) = "u"
charArray(57) = "v"
charArray(58) = "w"
charArray(59) = "x"
charArray(60) = "y"
charArray(61) = "z"
baseA = 28 'H1C
baseB = 24 'H18
Dim s1 As String
Dim s2 As String
s1 = "MTV"
s2 = "TMF"
Dim i As Long, j As Long
Dim f As Double
For i = 0 To 9 Step 1
charArray2(i) = i * i * i + 23
Next i
For i = 10 To 35 Step 1
baseA = baseA * 2
f = (baseA Xor Asc(s1)) + 5
charArray2(i) = f
baseA = baseA + 1
Next i
For i = 36 To 61 Step 1
baseB = baseB * 2
f = (baseB Xor Asc(s2)) + 62
charArray2(i) = f
baseB = baseB + 1
Next i
Dim sn As String
sn = txtCode.Text
Dim snArray(22) As String
Dim snlen As Integer
Dim snlen2 As Integer
snlen = Len(sn)
snlen2 = snlen + 1
For j = 1 To (snlen2-1) Step 1
snArray(j) = Mid(sn, j, 1)
Next j
Dim s3 As String
s3 = CStr(0)
Dim f3 As Double
f3 = 0
For i = 1 To (snlen2-1) Step 1
For j = 0 To 61 Step 1
'If snArray(i) = charArray(j) Then 'using array
If snArray(i) = Mid(charString, j + 1, 1) Then 'using string
f3 = Val(s3) + charArray2(j)
s3 = CStr(f3 * i)
End If
Next j
Next i
'MsgBox (CStr(s3))
Dim thankyou As String
Dim a1 As Integer, a2 As Integer
If Val(s3) = 616388714737576# Then
thankyou = "Thank you for registering"
a1 = Val(Mid(s3, 8, 1) + Mid(s3, 1, 1)) '16
a2 = Val(Mid(s3, 13, 1) + Mid(s3, 5, 1)) '58
End If
Call sub_41F430(thankyou, a1, a2)
End Sub
算法出来了,通过分析,主要就是查表求和并乘以相应的索引值,也没好办法反向计算,主要查表的索引是通过人为输入的,只有爆破计算unlock code 了,爆破代码如下,大概5秒就可以找到一个unlock code了。
代码是C++的,通过Dev-C++调试通过:
#include <iostream>
#include <string.h>
#include <time.h>
unsigned long long base[] = {
23, 24, 31, 50, 87, 148, 239, 366, 535, 752, //CrackMe中生成前10个数字,由(i * i * i + 23) 生成
122,68, 176, 392, 984, 1912, 3640, 7352, 14776, 29624, 59320, 118712, 237496, 475064, 950200, 1900472,
3801016, 7602104, 15204280, 30408632, 60817336, 121634744, 243269560, 486539192, 973078456, 1946156984,//// 中间26个数字
162, 116, 208, 536, 904, 1704, 3176, 6376, 12776, 25576, 51176, 102376, 204776, 409576, 819176, 1638376,
3276776, 6553576, 13107176, 26214376, 52428776, 104857576, 209715176, 419430376, 838860776, 1677721576 //// 最后26个数字
};
char sn_str[] = "0123456789ABCDEDGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
//char sn_ok = {'-'};
char sn_ok[] = "----------------------";
int sn_bit = {0};
int testSN(unsigned long long sn, int bit);
int main(int argc, char** argv) {
//unsigned long long sn = 4330972930LL;//616388714737576LL;
unsigned long long sn_result = 616388714737576LL;
sn_ok = '\0';
int n=0;
/// unlock code 最多20个字符,先取得与sn_result能整除几个索引值(也就是长度值)
for(int i=20; i>1; i--) {
if((sn_result % i) == 0) {
sn_bit = i;
}
}
/*
// n = 11,8,4,2, 最可能的是11,8位两种长度
11: 56035337703416,
8: 77048589342197,
4: 154097178684394,
2: 308194357368788,
*/
for(int i=0; i<n; i++) {
printf("n(%d) = %d\n", i, sn_bit);
}
time_t timep;
time (&timep);
printf("\nStart time: %s\n", ctime(&timep));
if(n>2) {
n = 2;/// 只测试 11位,8位unlockcode
}
for(int i=0; i<n; i++) {
sn_ok] = '\0';
printf("\ntest bits = %d\n", sn_bit);
int b = testSN(sn_result, sn_bit);
if(b == 1) {
printf("\nUnlock Code: %s\n", sn_ok);
break;
}
}
time (&timep);
printf("\nEnd time: %s", ctime(&timep));
//system("pause");
return 0;
}
int count = 0;
int testSN(unsigned long long sn, int bit) {
sn= sn / bit;
bit = bit - 1;
for(int j=61; j>=0; j--) {
unsigned long long sn1 = sn - base;
if(sn1<0) {
continue;
}
if((bit==0)) {
if((sn1==0)) {
sn_ok = sn_str;
//printf("sn_ok = %s\n", &sn_ok);
return 1;
} else {
continue;
}
}
if((sn1 % bit) == 0) {
sn_ok = sn_str;
//printf("bit=%d, sn_part = %s\n", bit+1, sn_ok);
int b = testSN(sn1, bit);
if(b==1) {
//printf("\nSN_OK: %s\n", sn_ok);
return 1;
} else {
sn_ok = '-';
}
}
}
return 0;
}
其运行结果如下:
n(0) = 11
n(1) = 8
n(2) = 4
n(3) = 2
Start time: Mon Apr 29 15:21:46 2019
test bits = 11
Unlock Code: LSgbo3t8zzz
End time: Mon Apr 29 15:21:51 2019
--------------------------------
Process exited after 4.65 seconds with return value 0
请按任意键继续. . .
输入生成的 unlock code: LSgbo3t8zzz
看到效果如下图:
完毕!!!!
附件是 noos.3 crackme 的VB源码。
本帖最后由 solly 于 2019-4-29 16:01 编辑
鬼手56 发表于 2019-4-29 15:37
请教一下 WKTVBDebugger不是只支持Windows 2000和NT吗?为什么你的W10可以用
不清楚,我直接可以用,就是要启动两次才可以(第一次闪退,第二次后就可以了)。我的是 Win10 x64 1809 版本。还就就是快捷键不可用,需要鼠标点,这个不方便,代码区也不能上下走,只能看它显示的代码。 鬼手56 发表于 2019-4-29 16:03
能否分享下你用的WKTVBDebugger 最近也在调P-Code,奈何一直找不到能用的WKTVBDebugger
是在 pediy.com下载的,可能是我把VB程序需要的相关库文件,包括 MSVBVM50.DLL,以及OCX和其它DLL 都放到应用程序目录,没有放到 windows 的 SysOnWOW64目录。 请教一下 WKTVBDebugger不是只支持Windows 2000和NT吗?为什么你的W10可以用 能否分享下你用的WKTVBDebugger 最近也在调P-Code,奈何一直找不到能用的WKTVBDebugger 写的很详细,谢谢分享 学习学习,谢谢楼主的分享,谢谢啦!!! 前来学习 哈哈哈哈哈 独狼 发表于 2019-4-29 20:41
哈哈哈哈哈
什么情况???{:1_904:}
页:
[1]
2