Skip navigation.

Fat R笔记……与减肥无关

Fat awful terrible Rubbish-bin

Posts tagged with "debugging"

orz...原来vb的native code是这样调试的……

这是在看雪精华4中看到的文章,总算是解决了我心中的疑惑...


标 题:解读 (6千字)
发信人:blowfish
时 间:2001-9-20 15:34:33
详细信息:

  VB DLL(主要指msvbvm50.dll、msvbvm60.dll)中导出的函数没有正式文档描述,既不知道传递给函数的参数是什么,也不知道函数返回什么值,这对从汇编层次上理解VB程序的算法和流程造成了一定的困难。
  VB程序使用的关键数据结构是VARIANT(MSDN中有描述),这是用作OLE Automation的一个通用结构,实际上是一个联合,其中可以存放各种标准的数据类型如LONG、BYTE、FLOAT、BSTR等。该结构中有一个标志用来指明在某一时刻该结构中具体存放的是哪种数据类型。不同的数据类型之间可以根据需要进行转换。
该结构的定义如下(#include <oaidl.h>)。其中vt就是用来表明实际存放的数据类型的标志。从该结构的偏移0x08处开始就是实际的数据。一般传给VB函数的参数都是某个VARIANT结构的指针,假定该指针的值放在寄存器eax中,则用"dd eax+8"(对于值类型)或"dd *(eax+8)"(对于指针类型)就可以看见传给该函数的实际参数值。知道这一点之后再去分析VB DLL中的函数相对来说就比较简单一些。

/* VARIANT STRUCTURE 
* 
*  VARTYPE vt;        //WORD 
*  WORD wReserved1; 
*  WORD wReserved2; 
*  WORD wReserved3; 
*  union { 
*    LONG          VT_I4 
*    BYTE          VT_UI1 
*    SHORT          VT_I2 
*    FLOAT          VT_R4 
*    DOUBLE        VT_R8 
*    VARIANT_BOOL  VT_BOOL 
*    SCODE          VT_ERROR 
*    CY            VT_CY 
*    DATE          VT_DATE 
*    BSTR          VT_BSTR 
*    IUnknown *    VT_UNKNOWN 
*    IDispatch *    VT_DISPATCH 
*    SAFEARRAY *    VT_ARRAY 
*    BYTE *        VT_BYREF|VT_UI1 
*    SHORT *        VT_BYREF|VT_I2 
*    LONG *        VT_BYREF|VT_I4 
*    FLOAT *        VT_BYREF|VT_R4 
*    DOUBLE *      VT_BYREF|VT_R8 
*    VARIANT_BOOL * VT_BYREF|VT_BOOL 
*    SCODE *        VT_BYREF|VT_ERROR 
*    CY *          VT_BYREF|VT_CY 
*    DATE *        VT_BYREF|VT_DATE 
*    BSTR *        VT_BYREF|VT_BSTR 
*    IUnknown **    VT_BYREF|VT_UNKNOWN 
*    IDispatch **  VT_BYREF|VT_DISPATCH 
*    SAFEARRAY **  VT_BYREF|VT_ARRAY 
*    VARIANT *      VT_BYREF|VT_VARIANT 
*    PVOID          VT_BYREF (Generic ByRef) 
*    CHAR          VT_I1 
*    USHORT        VT_UI2 
*    ULONG          VT_UI4 
*    INT            VT_INT 
*    UINT          VT_UINT 
*    DECIMAL *      VT_BYREF|VT_DECIMAL 
*    CHAR *        VT_BYREF|VT_I1 
*    USHORT *      VT_BYREF|VT_UI2 
*    ULONG *        VT_BYREF|VT_UI4 
*    INT *          VT_BYREF|VT_INT 
*    UINT *        VT_BYREF|VT_UINT 
*  } 
*/ 

标准数据类型VARTYPE的定义(#include <wtypes.h>):

typedef unsigned short VARTYPE; 

/* 
* VARENUM usage key, 
* 
* * [V] - may appear in a VARIANT 
* * [T] - may appear in a TYPEDESC 
* * [P] - may appear in an OLE property set 
* * [S] - may appear in a Safe Array 
* 
* 
*  VT_EMPTY            [V]  [P]    nothing 
*  VT_NULL            [V]  [P]    SQL style Null 
*  VT_I2              [V][T][P][S]  2 byte signed int 
*  VT_I4              [V][T][P][S]  4 byte signed int 
*  VT_R4              [V][T][P][S]  4 byte real 
*  VT_R8              [V][T][P][S]  8 byte real 
*  VT_CY              [V][T][P][S]  currency 
*  VT_DATE            [V][T][P][S]  date 
*  VT_BSTR            [V][T][P][S]  OLE Automation string 
*  VT_DISPATCH        [V][T][P][S]  IDispatch * 
*  VT_ERROR            [V][T][P][S]  SCODE 
*  VT_BOOL            [V][T][P][S]  True=-1, False=0 
*  VT_VARIANT          [V][T][P][S]  VARIANT * 
*  VT_UNKNOWN          [V][T]  [S]  IUnknown * 
*  VT_DECIMAL          [V][T]  [S]  16 byte fixed point 
*  VT_RECORD          [V]  [P][S]  user defined type 
*  VT_I1              [V][T][P][s]  signed char 
*  VT_UI1              [V][T][P][S]  unsigned char 
*  VT_UI2              [V][T][P][S]  unsigned short 
*  VT_UI4              [V][T][P][S]  unsigned short 
*  VT_I8                  [T][P]    signed 64-bit int 
*  VT_UI8                [T][P]    unsigned 64-bit int 
*  VT_INT              [V][T][P][S]  signed machine int 
*  VT_UINT            [V][T]  [S]  unsigned machine int 
*  VT_VOID                [T]        C style void 
*  VT_HRESULT            [T]        Standard return type 
*  VT_PTR                [T]        pointer type 
*  VT_SAFEARRAY          [T]        (use VT_ARRAY in VARIANT) 
*  VT_CARRAY              [T]        C style array 
*  VT_USERDEFINED        [T]        user defined type 
*  VT_LPSTR              [T][P]    null terminated string 
*  VT_LPWSTR              [T][P]    wide null terminated string 
*  VT_FILETIME              [P]    FILETIME 
*  VT_BLOB                  [P]    Length prefixed bytes 
*  VT_STREAM                [P]    Name of the stream follows 
*  VT_STORAGE                [P]    Name of the storage follows 
*  VT_STREAMED_OBJECT        [P]    Stream contains an object 
*  VT_STORED_OBJECT          [P]    Storage contains an object 
*  VT_BLOB_OBJECT            [P]    Blob contains an object 
*  VT_CF                    [P]    Clipboard format 
*  VT_CLSID                  [P]    A Class ID 
*  VT_VECTOR                [P]    simple counted array 
*  VT_ARRAY            [V]          SAFEARRAY* 
*  VT_BYREF            [V]          void* for local use 
*  VT_BSTR_BLOB                      Reserved for system use 
*/ 

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_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_BSTR_BLOB    = 0xfff, 
    VT_VECTOR    = 0x1000, 
    VT_ARRAY    = 0x2000, 
    VT_BYREF    = 0x4000, 
    VT_RESERVED    = 0x8000, 
    VT_ILLEGAL    = 0xffff, 
    VT_ILLEGALMASKED    = 0xfff, 
    VT_TYPEMASK    = 0xfff 
    }; 


  VB DLL还调用了oleauto32.dll中的部分函数。oleauto32.dll是个通用的proxy/stub DLL,其每个函数的原型在<oleauto.h>中定义,并在MSDN中有详细描述。这也有助于理解VB DLL中的函数的作用。

举例:

LEA EAX, [EBP-58] 
PUSH EAX 
CALL [MSVBVM60!__vbaI4Var] 

执行call之前敲dd eax+8,得到的值为3;
执行完call之后,eax = 3
从而可知__vbaI4Var的作用是将一个VARIANT转换为I4(即一个长整数)。




简单来说,根据VARIANT的结构,在调试中看到的地址如果直接d xxxx是看不到变量真实的数值的(看到的是VARTYPE),要加上8的偏移量才行(也就是要跳过那4个长度为WORD的变量)。认识到这一点,用OD调试Native程序就简单多了(SmartCheck实际上得到的是一个VB函数调用的log,虽然方便,但似乎log得不太完整,所以如果要对代码进行分析,还是要用OD)。

简单记录一下某个程序的调试:
这个程序的注册对话框非常简单,一项是用户名,一项是根据用户名生成的序列号,下面就是key,输入错误的key会弹出提示对话框。
首先 bp rtcMsgBox 对MsgBox调用设置断点,拦截下来后用ctrl+f9运行到返回(alt+f9似乎用不了,可能是因为msvbvm60.dll跟执行文件放在同一目录下,被OD认为也是用户代码)
返回到这里:
0045BC6A   .  51            push    ecx
0045BC6B   .  FF15 90104000 call    near [<&MSVBVM60.#595>]          ;  MSVBVM60.rtcMsgBox
0045BC71   .  8D95 5CFFFFFF lea     edx, [ebp-A4]
0045BC77   .  8D85 6CFFFFFF lea     eax, [ebp-94]

向上看可以看到一个跳转的终点
0045BBE1   . /E9 2E010000   jmp     app.0045BD14
0045BBE6   > |B9 04000280   mov     ecx, 80020004
0045BBEB   . |B8 0A000000   mov     eax, 0A

弹出对话框的那段代码就是从0045BBE6开始执行的了。再上一行可以看到一个jmp,这个jmp会跳过MsgBox这段代码,也就是说,前面应该有一个判断调转,如果key错误,则跳到0045BBE6,否则继续执行,直到0045BBE1的jmp指令。

看看是从哪里跳到0045BBE6的。鼠标点中0045BBE6那行,可以看到:
跳转来自 0045AF41

从右键菜单就可以跳到那个判断语句了:
0045AF3A   .  66:399D BCFEF>cmp     [ebp-144], bx
0045AF41   .  0F84 9F0C0000 je      app.0045BBE6


是比较[ebp-144]和bx的值来决定跳转的。一时难以看出bx怎么得来的,但再向上,可以看到[ebp-144]的来源:
0045AEC6   .  50            push    eax
0045AEC7   .  51            push    ecx
0045AEC8   .  895D A8       mov     [ebp-58], ebx
0045AECB   .  C785 2CFFFFFF>mov     dword ptr [ebp-D4], 8008
0045AED5   .  FF15 E4104000 call    near [<&MSVBVM60.__vbaVarTstEq>] ;  MSVBVM60.__vbaVarTstEq
0045AEDB   .  8985 BCFEFFFF mov     [ebp-144], eax

嗯,这个MSVBVM60.__vbaVarTstEq从字面上看是比较字符串。按[F2]对call下断点,重新点一次注册按键。

拦截下来的时候可以在堆栈窗口看到它的参数:
0013F344  0013F434  |Arg1 = 0013F434
0013F348  0013F484  \Arg2 = 0013F484


两个参数是通过那两条push eax和push ecx指令压入stack的。可是 d eax看到的数值是
0013F484 08 00 13 00
而d ecx看到的是
0013F434 08 80 00 00
这个既不是字符串,也不像一个指针(根据上面那篇文章,其实0008是VT_BSTR的意思...嗯,可是为什么下面那个是8008呢?@)。但是加上8字节的偏移量后呢?
d eax+8:
0013F48C 74 E3 15 00
d ecx+8:
0013F43C 64 C4 15 00
现在看起来是指针了。用d [eax+8]和d [ecx+8],果然看到了Unicode形式的真实key和自己输入的伪key。

继续逆着向上,结合SmartCheck得到的信息,就可以很容易地分析出Key的计算方法,写keymaker了。

嗯哼,貌似把它crack掉了

OD用上瘾了,于是一不做二不休,继续研究一下那个客户端

现在大概搞清楚了后面两个参数的意义。其实密钥只是第三个参数,也就是说程序根据第三个参数的十六进制串产生一个表,然后再根据这个表还原授权文件。

而第四个参数纯粹是为了跟第三个参数匹配。第三个参数是登陆模块登陆服务器后服务器返回的TimeCode.程序在开始运行的时候会根据第三个参数产生一个字符串,然后再通过一些很恶心的操作生成一个16字节的十六进制串,再把它转化为32字节的ASCII字符串,然后比较第四个参数与这个生成的字符串是否相同,不同则退出。

不过调试的时候老是发生异常,刚开始以为程序还有陷阱,但尝试过多种方法(甚至考虑修改代码,让它用计算得到字符串替换掉第四个参数),还是解决不了。后来看了一下崩溃时的call stack,发现还是xml解析的时候出错,这才想起是因为后面还原xml的代码没有patch,我的授权文件被我替换过忘了换回来,而且我的第三个参数没有用匹配授权文件的那个,所以当然不能正确还原授权文件啦。于是把授权文件换回来,重新设好参数,结果只需要把判断第四个参数比较结果是否一致的jnz干掉,就顺利地进去了,一点问题都没有。而因为后面的代码patch过后,第三个参数也没有影响了,所以用/0 /0作最后两个参数就ok了。

写个PPatcher脚本,如下:
#Process Patcher Configuration File
Version=4.00
PatchInformation=Client.Core Process Patcher
PatchAuthor=FatR
PatchContactInformation=
DisplayName=Client.Core Loader
Filename=Core.exe
Filesize=3822592
Arguments=/ON /Returner /0 /0
#xor [esi], eax
Address=0x4F43BF:0x31:0x90
Address=0x4F43C0:0x06:0x90
#mov [esi+4], edx
Address=0x4F43FA:0x89:0x90 
Address=0x4F43FB:0x56:0x90
Address=0x4F43FC:0x04:0x90
#mov [esi], edx
Address=0x4F4432:0x89:0x90
Address=0x4F4433:0x16:0x90
#xor [esi+4], eax
Address=0x4F444A:0x31:0x90
Address=0x4F444B:0x46:0x90
Address=0x4F444C:0x04:0x90
#mov [esi], edx
Address=0x4F4486:0x89:0x90
Address=0x4F4487:0x16:0x90
#mov [esi+4], edx
Address=0x4F44BD:0x89:0x90 
Address=0x4F44BE:0x56:0x90
Address=0x4F44BF:0x04:0x90
#xor [esi], eax
Address=0x4F44CA:0x31:0x90
Address=0x4F44CB:0x06:0x90
#jnz -> jmp
Address=0xC317D0:0x75:0xEB
#End of Configuration File
 

用它作loader就可以顺利启动客户端了

暴郁闷啊……

, ,

今天下了另外一个汉化版本的OllyDbg(ODbyDYK)
把之前那个数据库客户端的UDD文件拷了过去,并设好参数
然而运行的时候却发现设置的断点没有起作用,程序并没有被拦下来,而是直接进入了程序界面,且仿佛在提示栏那里看到一条错误提示一闪而过
顿时觉得非常郁闷,难道是这个版本的兼容性有问题?
经过替换OD执行文件、Plugin、ini文件后,发现问题出在Ollydbg.ini上
经过n小时的反复对比替换,最后竟然发现,问题出在……出在……出在[Arguments]字段…………
………………-__________-
仔细一看,那行是这样
Executable[0]=Argument[0]=/ON /USERNAME /.........

估计是因为复制的时候复制的是
Argument[0]=/ON /USERNAME /.......

而我本来是应该复制到ODbyDYK的ini里的,却错手复制到了“参数”菜单项里,所以写进ini里就变成了上面那副样子……
狂分特啊,居然是这个原因,真是郁闷死我了啊……T_T
而那个被调试的程序之所以还能进入主界面,是因为它把第一个参数认成了Argument[0]=/ON ,当然这个参数它不认识,所以就以离线方式运行了
唉,为什么我老是被粗心大意这个毛病拖累呢

OllyDbg

,

大家看到我用OllyDbg,都以为我在勤奋工作……偷笑一下,嘻嘻……

总算有点上手了,这次用到的功能:脚本、断点、补丁、注释、Watch,还有一些快捷键。
常用的快捷键是F7(Trace Into)、F8(Trace Over)、F9(Run)、Ctrl+F2(Restart)、Ctrl+A(Analyze)、*,Ctrl+*,以及各个窗口的快捷按键

不知为何,设置的内存断点在"断点"窗口里面看不到……却能正常工作
所以还是用硬件断点好了

Analyze非常有用,代码一塌糊涂的时候Ctrl+A就清晰多了

有点不爽的地方就是当程序加了壳,Ctrl+F2后,所有的断点和补丁都会被禁掉
所以一般都是Ctrl+F2 -> 运行脚本,去到OEP -> alt+b狂按空格激活断点(双击断点还可以直接在反汇编窗口跳到断点所在位置) -> Ctrl+P狂按空格激活补丁 -> F9运行

至于内存搜索,实在不懂到底怎么回事,只好用Winhex来搜。Alt+F9就可以打开它的内存编辑器了
然后在OD里面Ctrl+G跳转

=================06/03/06====================
嗯,帮助文档里面是这么说的
Alt+F7 -转到上一个找到的参考。
Alt+F8 -转到下一个找到参考。

现在有点明白“参考”是怎么回事了,其实也可以看成“引用”
例如对于如下语句
004F42D7 |. E8 94EEF0FF call Core.00403170
在这里选择右键菜单"查找参考对象"->"调用目的地址",就会在弹出窗口"参考位于Core:CODE到00403170"中出现所有使用了call Core.00403170的地方
之后,在任何地方按alt+F7/F8,就可以在不同的Call Core.00403170指令的地址间切换
可以想到的一个应用就是找到某个关键的call,就可以用此功能看看到底有哪些地方调用了这个call
例如,我要EB 03 90 90 90某个call,就可以用此功能找出所有调用了这个call的地方,并统统屏蔽掉