solly 发表于 2020-6-27 18:01

某DBISAM文件型数据库管理小工具的试用版破解过程详细记录

本帖最后由 solly 于 2020-6-28 23:33 编辑

这个小工具软件是一个 DBISAM 文件型数据库的管理工具,可以查看 DBISAM 格式的文件数据,软件名称为 DBISAM Viewer。
这个软件的试用版有一些限制:
1、时间限制,过期后不可用;
2、每次打开文件时会弹出 NAG 窗口;
3、查询的记录条数有限制,超过100条及以上数据不显示;
4、条件过滤无效;
5、导出文件也有100条限制,还有一个水印显示,并且禁止在"Options"界面中取消水印;
6、主窗口显示“(UNREGISTERED)”,About 窗口显示“未注册/演示版/只能查询前100条记录”等信息。


首先查看文件信息:

显示是 Delphi 编译的,“Scan /t” 再查一下:

显示了具体的 Delphi 版本为 3.0。程序的节信息如下:

标准的 delphi 程序。我们用 DeDe Dark 打开程序,查看 Form 和 事件 等信息:

DeDe 分析完成,表单信息如下:


上图是 TfrmMain 主表单的信息,事件过程的地址指针数据。

如上图所示,对事件进行排序一下,可以看到最前面的几个事件是 Form 的事件,对我们有用的主要是 FormCreate 和 FormActivate 两个事件。


首先看看 Form 的FormActivate 事件,如下图所示:

可以看到,这一段代码就是对时间进行限制的。下面再看看 FormCreate 事件:

继续:

主要是初始化一些界面显示,标题栏和状态栏字符串等。


现在开始跟踪调试,用 OD 载入程序,首先Ctrl+G跳转到 FormActivate 事件,如下图所示:


如下图所示,就是 TfrmMain 表单的 FormActivate 事件的代码,在 0x004B126B 处下一个断点,并按 F9 执行程序:

程序中断在所下的断点处,按 F8 执行代码到下图所示位置:

这里是一个时间转换函数,将 year, month, day 转换成 Delphi 的 TDateTime 格式(2021-08-07,实际上是一个双精度浮点数:44415.0)。


然后再取得系统时间,与转换后的时间比较:

如果当前系统时间大于 2021-08-07,则表示过期,程序会弹出如下提示:

然后程序会退出。


首先破解这个时间限制,如下图所示:


将 jbe 指令改成 jmp 指令即可。具体改为如下所示(修改的是最后一条指令):
004B129D   .DC5D F8       fcomp   qword ptr             ;时间比较
004B12A0   .DFE0          fstsw   ax
004B12A2   .9E            sahf
004B12A3      EB 45         jmp   short 004B12EA                   ;时间过期限制


跳过时间检查后,进入主界面,如下图所示:

按“Open File” 打开一个 *.DAT 文件,会弹出一个 NAG 窗口(其实就是 About 窗口),如下图所示:

在 DeDe Dark 中找到这个 "Open File" 事件,如下图所示:

该事件调用了另外一个 btnLoadClick() 事件,业务处理都是在这个btnLoadClick()事件中进行的。在 OD 中跳转到 "Open File" 事件,如下图所示:


我们在 btnLoadClick() 中下一个断点(在 0x004AAE2B 处),然后再次打开一个 *.DAT 文件,如下图所示:

点“打开”后,OD 会中断程序:

一路按 F8 执行,执行完整个函数,也没有弹出 NAG ,如下图所示,已退出 btnLoadClick() 函数:

看来不在此函数内处理的NAG显示,F9 运行,可以听到“咣”的一声后,NAG 如期弹出。还好有这个“咣”的一声,我们在 OD 的命令窗口下断点 "bp MessageBeep",然后再一次打开一个*.DAT文件,这次会断在如下位置,如下图所示:


这是中断在系统 MessageBeep 函数中了,一路 F8 退出函数,如下图所示:


再一次F8返回上一级函数,如下图所示:

具体代码如下:
004AC278   .53                  push    ebx
004AC279   .8BD8                mov   ebx, eax
004AC27B   .80BB 78030000 00    cmp   byte ptr , 0x0      ;检查是否要显示 NAG
004AC282   .74 19               je      short 004AC29D
004AC284   .8BC3                mov   eax, ebx
004AC286   .E8 9DE7FFFF         call    004AAA28                         ;beep()
004AC28B   .C683 78030000 00    mov   byte ptr , 0x0
004AC292   .8B83 38020000       mov   eax, dword ptr
004AC298   .8B10                mov   edx, dword ptr
004AC29A   .FF52 2C             call    dword ptr              ;显示 NAG
004AC29D   >5B                  pop   ebx
004AC29E   .C3                  retn
可以看到,是通过判断 处的值来决定是否显示 NAG 窗口的(0:不显示;1:显示),对该处地址下一个硬件中断,如下图所示:

然后再一次打开一个 *.DAT 文件,首先中断在如下位置:

这里是初始化为 0,所以不是这里改的状态值,F9 继续执行,再一次硬件中断在如下位置:

这里是在 btnLoadClick() 事件中的代码,也是在这里决定是否显示NAG 窗口。

再一次 F9 运行,再次来到了显示 NAG 代码处,如下所示:这里是将此标志重新设置为 0,表示不再显示(这里其实是3个 delphi 的 StringGrid 显示刷新的公共事件,会执行3次,置 0 是保证只显示一次)。至此,就是 btnLoadClick() 中通过置 的值来决定是否显示 NAG。
因此,只要将前面置1的指令修改成置0就可以了,如下图所示(同时可以删除硬件中断了):具体修改位置如下:004AB29D      C680 78030000 00    mov   byte ptr , 0x0      ;是否显示 NAG, 1-显示,0-不显示
004AB2A4   .C3                  retn                                     ;RET 用作跳转到 004AB2AC

这样修改后,就不会再弹出 NAG 窗口。
下面继续处理 About 窗口中的显示,点 "About " 按钮,会弹出 About 对话框,如下图所示(NAG显示也是这个窗口):这里显示“未注册/演示版/试用版只能显示100条记录”,并且,在OK按钮左边还显示了 30 天试用期什么的。。。。。
通过 DeDe 查得 About 弹出事件,然后直接CTRL+G 跳转到事件中去,如下图所示:事件的开始代码如下所示:在 0x004B13D1 处下一个断点,再在软件界面点“About”,就会中断在上图所示位置。
F8执行代码,上面是修改标签显示一个大大的重影文本标签(实际上是两个 Label 移位叠加)。F8 继续往下走:
还是显示一些信息,继续往下走:可以看到,这里是一个解码函数,eax 指向一条编码后的字符串,如中上图中的 OD 数据区所选的数据。F8 执行解码函数,如下图所示:解码的内容为:02484EE0You may use shareware version for 30 days (TRIAL period).
F8 继续执行,下面 ecx 又指向了一条编码的字符串,如下图所示:
后面继续解码,如下图所示(edx指向的字符串):解码得到:02487BF8After that period has ended, to continue using this product you
02487C38have to purchase the Registered version.

就是显示在“OK” 按钮左边的内容,合并后如下图所示:同时调用函数显示:004B1513|.8B55 F8             mov   edx, dword ptr
004B1516|.8B45 FC             mov   eax, dword ptr
004B1519|.8B80 FC010000       mov   eax, dword ptr
004B151F|.E8 90FAF6FF         call    <SetText()>
我们把最后一条指令 nop 掉,就不会显示了。
接下来还是解码字符串,如下图所示,解码“未注册/演示版/”等信息:解码后:生成 100 条限制字符串:
再次解码:解码后为一个 Format() 函数的参数字符串,如下图所示,包含一个%s 参数,就是代入前面的 100 生成的字符串 "100",如下图所示:代入后如下图所示:再次合并字符串,用于显示 About 中的红色字符串:最后就是显示 About 窗口了:如下所示,框框中显示的就是前面解码合并的两条字符串。首先,我们把上图中的第(2)条显示去除,如下图所示,将 call setText() 指令 NOP 掉:具体修改如下,将 0x004B151F 处指令 NOP 处理:004B1513|.8B55 F8             mov   edx, dword ptr
004B1516|.8B45 FC             mov   eax, dword ptr
004B1519|.8B80 FC010000       mov   eax, dword ptr
004B151F      90                  nop
004B1520      90                  nop
004B1521      90                  nop
004B1522      90                  nop
004B1523      90                  nop
再次点“About”,则显示如下:

另外一条显示(红色字符串),暂不处理,放后面再处理。

现在我们来处理最多只显示 100 条记录的限制,如下图所示,当多于100条记录时,只显示100条:

首先在 DeDe Dark 中查看事件,找到 StringGrid 控件的刷新事件,如下图所示:

在 OD 中跳转到该事件,并下一个断点,断点我下在0x004BD435,如下图所示:


点一下主界面的记录,变动一下活动的记录号,马上会中断下来,如下图所示:

上图中的 就是指的 StringGrid 控件,这个在 DeDe Dark 中或者IDR中可以看到。
先切换 OD 数据区的内容到 StringGrid 对象实例内存区内,查找 Delphi 控件 StringGrid 的 rowCount 属性值,如下图所示:

用过 Delphi 的编程者就知道,StringGrid 的 RowCount 包含标题行,所以是 0x65(101)行,我们输入 "65 00" 查询,如下所示,就找到了存放 rowCount 属性存放位置,在这个位置下一个硬件写入断点,如下图所示:

把上面的 F2 断点禁用,按 F9 回到程序,重新打开一个数据文件,如软件自带 Orders.DAT,马上就会硬件中断下来,如下图所示:

这里是写入实际的条数,还没有限制为 100 条,F9 继续执行:


这次写入的是 0x65 条了,看看这个 ESI 的值是如何来的,往上回溯,如下图所示:

是通过 ECX 传递过来的,这个 ECX 在 Delphi 就是函数的参数,也就是説这个函数就应该是 Delphi 的修改 RowCount 值的函数,CTRL+F9 退出函数,进入如下所示代码区域:

可以看到,前面是对 RowCount 数据进行校验或检查,再调用函数修改数据,再次 CTRL+F9 返回,来到下面位置,可以看到,我们要找的地方找到了:

具体代码如下:
004AA6C3   .8B83 EC010000      mov   eax, dword ptr
004AA6C9   .83B8 58010000 65   cmp   dword ptr , 0x65      ;加上标题行,共 101 行
004AA6D0   .7E 0A                jle   short 004AA6DC                   ;破解显示100条限制
004AA6D2   .BA 65000000          mov   edx, 0x65                        ;限制显示行数
004AA6D7   .E8 FC90F9FF          call    004437D8
004AA6DC   >A1 B44B4C00          mov   eax, dword ptr


在这里检查如果 rowCount 大于 0x65 则将 rowCount 改成 0x65。破解也简单,直接跳过修改即可,如下图所示:

具体修改如下:
004AA6C9   .83B8 58010000 65   cmp   dword ptr , 0x65      ;加上标题行,共 101 行
004AA6D0      EB 0A                jmp   short 004AA6DC                   ;破解显示100条限制


我们先删除硬件写入中断,再 F9 回到软件,重新打开刚才打开的数据文件,这次 rowCount 可以超出 100 了,但还没有显示数据,只显示空的格子,如下图所示:

由上图可见,数据可以显示 101 条,即 0x65 条,所以,还需要解决数据记录条数不超过101条的问题。

再次回到 DeDe Dark,如下图所示:


可以看到一个 StringGrid 单元格自绘事件,双击进去看看:

再一个一个函数打开看看,如上图中红框中的函数,看看会有什么有用的发现,如下图所示:


手气好,可以看到,上图中有一条与0x65 进行比较的指令,大于则跳转,与前面的 rowCount = 0x65 类似,再次转入 OD,来到该代码处,如下图所示:

在这里下一个断点,位置:0x004AA3AD,可以看到,大于 0x65 时,直接跳出函数了,因此我们把 jg 跳转指令先 NOP 掉,如下图所示:

具体改动如下:
004AA3AD   .837D 10 65         cmp   dword ptr , 0x65       ;限制读取记录数
004AA3B1      90                   nop
004AA3B2      90                   nop
004AA3B3      90                   nop
004AA3B4      90                   nop
004AA3B5      90                   nop
004AA3B6      90                   nop
004AA3B7   .C645 FF 00         mov   byte ptr , 0x0


回到软件,再次打开一个数据文件(orders.DAT),可以看到,可以显示101条以上的记录数据了,这个就是运气好吧:



该工具有一个查询过滤(Filter)功能,但是试用版没有生效,过滤条件没有执行,先在 DeDe Dark 中找到这个事件(miFilterClick)位置,如下图所示:


然后我们执行一下过滤功能,如下图所示:

但现在点“OK”没有用,在事件中下一个断点,如下图所示:

问题出在这里,这里总是返回 0,然后就跳过后面的循环过滤代码了,我们修改代码,强行返回 StringGrid 的行数即可。如下图所示:

正好下面有两行读取 StringGrid 的 RowCount 的代码,对照修改一下就可以了,具体修改如下所示:
004BD30C      8B80 EC010000      mov   eax, dword ptr        ;
004BD312      8BB0 58010000      mov   esi, dword ptr
004BD318   .85F6               test    esi, esi
004BD31A   .7E 67                jle   short 004BD383


再一次执行过滤功能,就可以用了:

不过该软件实现得不是太好,就是隐藏一下不符合要求的记录。

下面来处理软件导出数据的限制,导出时有些格式也会有100条记录的限制,同时会有一条水印(注释或标题)导出时不能修改。
如下图所示,先在 DeDe 找到事件入口:

在 OD 中跳转到该事件,并下一个断点(0x004B66F3),在软件中点“File"->"Save As",就会断下来:


F8 来到如下图所示位置,下两个断点,因为中间那条 call 0x0042F428 是显示导出窗口的,按F8执行会卡死(Delphi程序的通病)只能 F9 执行。


具体代码如下:
004B6A40   .8B45 E0       mov   eax, dword ptr
004B6A43   .E8 E089F7FF   call    0042F428
004B6A48   .48            dec   eax
004B6A49   .0F85 DA010000 jnz   004B6C29

F9 后,弹出如下对话框:

先择一个保存的类型,按”确定“,如上图所示,我选择了 XML 格式。再次中断在前下的第2个断点,再 F8 执行到如下图所示位置:


这里的 al 就是我们在界面上选择的类型值,al==3 表示选择的第4项:XML格式。具体代码如下:
004B6B6B   .8A80 24010000 mov   al, byte ptr
004B6B71   .8845 DF       mov   byte ptr , al
004B6B74   .8B45 E0       mov   eax, dword ptr
004B6B77   .8B80 60020000 mov   eax, dword ptr
004B6B7D   .E8 3E15F8FF   call    004380C0


再一路F8,来到如下位置,这里开始处理数据保存了:


在这里按 F7 进入函数,具体代码如下:
004B6C64   .33C0          xor   eax, eax
004B6C66   .8A45 DF       mov   al, byte ptr
004B6C69   .8B15 4C4A4C00 mov   edx, dword ptr           ;datview.004C4664
004B6C6F   .8B0C82      mov   ecx, dword ptr
004B6C72   .8B55 F4       mov   edx, dword ptr
004B6C75   .8B45 FC       mov   eax, dword ptr
004B6C78   .E8 CFF4FFFF   call    004B614C


进入 0x004B614C函数后,会来到一个分支跳转处(switch),如下图所示,按不同的选择类型跳转:

跳转后,会来到如下位置:

这里就是处理 XML 格式导出的地方了。再次按 F7 进入 call 0x004B3364,一路 F8 来到如下位置,可以看到 100 条限制的指令:


上图中用红框框中的指令,就是其它类型导出时,限制100条记录的特征码,可以通过搜索这个特征码找到其它格式导出时的限制。
去除限制也简单,把 jg 指令 NOP 掉即可,如下图所示:


具体修改如下:
004B3660   > \8BC6          mov   eax, esi
004B3662   .2B45 10       sub   eax, dword ptr
004B3665   .83F8 65       cmp   eax, 65                        ;限制导出XML行数
004B3668      90            nop
004B3669      90            nop
004B366A      90            nop
004B366B      90            nop
004B366C      90            nop
004B366D      90            nop
004B366E   .C645 E6 01    mov   byte ptr , 1


其它几种格式的限制指令位置如下,方便大家定位:
004B25BC   cmp   eax, 65                     限制导出txt行数
004B2DE7   cmp   eax, 65                     限制导出html行数
004B3665   cmp   eax, 65                     限制导出XML行数
004B4064   cmp   eax, 65                     限制导出JSON行数
004B4D77   cmp   eax, 65                     限制导出为XLS行数
004B9521   cmp   eax, 65                     限制导出SQL行数
我们导出成 XML 文件后,打开看看,如下图所示:


选中的那一行就是所谓的水印,不过这条注释可以在软件的选项设置中修改,但由于是未注册版,禁止修改,强行附加并不允许修改设置,如下图所示:

所以,导出的文件中的总是会带有”Generated by DBISAM Viewer (Scalabium Software, http://www.scalabium.com/dbisam) “这一条水印信息,现在我们去除这个限制。

我们这次在 TfrmMain 的 FormCreate 事件中,找到这两个设置的初始化过程,如下图所示:


因为这个初始化是启动时处理的,我们先把前面对程序的修改保存下来,不然重启软件就没有了,如下图所示,选择右键菜单:

”复制到可执行文件“->”所有修改“,弹出如下确认框:

选择”全部复制“即可。


再右键”保存文件“。这样就把我们前面的修改保存下来了。

退出软件,重新用 OD 载入我们前面修改后保存的软件。

前面説了,这条水印其实可以在Options中修改,只是因为试用版限制修改了而矣,我们先在 DeDe 中找到设置的事件入口:

事件名为:miOptionsClick(),入口地址是 0x004B7D64。
在 OD 中跳转到该事件函数中,如下图所示,我可以看到初始化显示界面的代码,如下图所示:


但是再往后,却没有找到保存该设置的代码:


因此,我们需要重新补上这段代码,不然,还是无法修改并保存这个设置。
首先我们先找到一段类似的保存设置的代码,在这个代码基础上进行修改,如下图所示中选中的代码就是我们需要的:


具体代码如下所示:
004B819F   .8B45 F8       mov   eax, dword ptr
004B81A2   .8B80 88020000 mov   eax, dword ptr
004B81A8   .E8 4B10F8FF   call    004391F8
004B81AD   .8843 5A       mov   byte ptr , al
004B81B0   .8D55 E8       lea   edx, dword ptr
004B81B3   .8B45 F8       mov   eax, dword ptr
004B81B6   .8B80 8C020000 mov   eax, dword ptr
004B81BC   .E8 C38DF6FF   call    00420F84
004B81C1   .8B55 E8       mov   edx, dword ptr
004B81C4   .8D43 5C       lea   eax, dword ptr
004B81C7   .E8 70B8F4FF   call    00403A3C


我们就是要在这段代码的基础上来操作。我们需要在程序代码节的最后面,找一段空白处,依据上面代码,写入我们的代码,在 0x004BF660 处找到一处可以保存代码的空白空间。
同时,由于有代码重定位,我们在这个函数内要找一处不含重定位的5个字节代码写入跳转指令,跳转到我们的代码处,在函数后面保存数据最后处,找到了5字节,如下图所示位置:

代码位置如下:
004B8399   .33C0          xor   eax, eax
004B839B   ?5A            pop   edx
004B839C   ?59            pop   ecx
004B839D   ?59            pop   ecx


正好5字节,也没有重定位,我们在该位置写入跳转指令:jmp 0x004BF660。跟随该指令,来到空白处,补上我们的代码,如下图所示:


具体代码如下:
004BF660      8B45 F8          mov   eax, dword ptr
004BF663      8B80 4C020000    mov   eax, dword ptr
004BF669      E8 8A9BF7FF      call    004391F8
004BF66E      8843 51          mov   byte ptr , al
004BF671      8D55 E8          lea   edx, dword ptr
004BF674      8B45 F8          mov   eax, dword ptr
004BF677      8B80 50020000    mov   eax, dword ptr
004BF67D      E8 0219F6FF      call    00420F84
004BF682      8B55 E8          mov   edx, dword ptr
004BF685      8D43 54          lea   eax, dword ptr
004BF688      E8 AF43F4FF      call    00403A3C
004BF68D      90               nop
004BF68E      33C0             xor   eax, eax
004BF690      5A               pop   edx
004BF691      59               pop   ecx
004BF692      59               pop   ecx
004BF693      90               nop
004BF694    ^ E9 058DFFFF      jmp   004B839E
004BF699      90               nop


另外,我们刚才在 0x004B839B 是函数退出代码,当在设置界面选择”Cancel"时,会跳转到这里,所以,我们还要修改如下所示位置的代码:


具体代码如下:
004B808E   .48               dec   eax
004B808F      0F85 F9750000    jnz   004BF68E

现在,保存数据的代码可以了,但还有一个问题,界面的控件状态是 disabled 状态,不可更改。还是的 miOptionsClick() 事件中,有对这两个控件状态进行处理的代码,如下图所示:


将这里两个call调用的dl参数赋值为 0x01即可,如下图所示:

具体修改后的代码如下:
004B7EFE   .8B45 F8          mov   eax, dword ptr
004B7F01   .8B80 4C020000    mov   eax, dword ptr
004B7F07      B2 01            mov   dl, 1
004B7F09   .E8 0E90F6FF      call    00420F1C
004B7F0E   .8B45 F8          mov   eax, dword ptr
004B7F11   .8B80 50020000    mov   eax, dword ptr
004B7F17      B2 01            mov   dl, 1
004B7F19   .E8 FE8FF6FF      call    00420F1C

现在我们先保存这次的修改,因为需要重启才能验证是否有效:

按前面的方式保存代码,重新在 OD 中载入程序,F9 运行,点菜单“View”->"Options"打开选项窗口:

可以看到,现在可以修改了。我们修改后,重新启动程序,发现修改的内容又恢复原状了,看来还有暗桩存在,在 TfrmMain.FormCreate 事件中,前面初始化控件后,在读取设置信息后,又强行将这两个信息处理了:


因此,我们需要跳过这段代码,去除暗桩,修改如下:注意要改动没有重定位的代码:


具体代码如下:
004AE53D   .A1 884B4C00      mov   eax, dword ptr
004AE542      EB 47            jmp   short 004AE58B
004AE544      90               nop
004AE545      90               nop


修改后,再次保存代码到文件:

再次重新在 OD 中载入保存的程序,并 F9 运行,这次可以保存修改值了:

再次重启程序,可以看到,能正确读取这些保存的数据了。

功能方面的问题基本处理完了,现在开始处理显示方面的问题,显示方面主要是修改数据节的数据,不需改动代码了。

首先我们修改主界面标题显示“(UNREGGISTERED)”的问题。在 TfrmMain.FormCreate() 事件中,有如下代码:


具体代码如下:
004AE4C7   .FFB5 60FEFFFF    push    dword ptr             ;"Direct DBISAM table reading"
004AE4CD   .68 24F74A00      push    004AF724                         ;   (
004AE4D2   .8D95 6CFEFFFF    lea   edx, dword ptr
004AE4D8   .A1 1C4D4C00      mov   eax, dword ptr
004AE4DD   .8B00             mov   eax, dword ptr
004AE4DF   .E8 8CA3FFFF      call    <DecodeString()>               ;Decode
004AE4E4   .FFB5 6CFEFFFF    push    dword ptr
004AE4EA   .68 FCF64A00      push    004AF6FC                         ;)
004AE4EF   .8D85 64FEFFFF    lea   eax, dword ptr
004AE4F5   .BA 04000000      mov   edx, 4
004AE4FA   .E8 2558F5FF      call    00403D24
004AE4FF   .8B95 64FEFFFF    mov   edx, dword ptr
004AE505   .8B45 FC          mov   eax, dword ptr
004AE508   .E8 A72AF7FF      call    <SetText()>                      ;setText("(UNREGISTERED)")

中间调用了一个解码函数,该函数如下图所示:


函数主体是一个循环,在循环内调用了另外一解码函数,如下图所示:

再进入这个函数,如下图所示:


具体代码如下:
004A8848/$B1 01            mov   cl, 1
004A884A|.B2 07            mov   dl, 7
004A884C|.E8 1BFEFFFF      call    004A866C                         ;swapbit()
004A8851|.B1 02            mov   cl, 2
004A8853|.B2 03            mov   dl, 3
004A8855|.E8 12FEFFFF      call    004A866C
004A885A|.B1 04            mov   cl, 4
004A885C|.B2 05            mov   dl, 5
004A885E|.E8 09FEFFFF      call    004A866C
004A8863|.B1 06            mov   cl, 6
004A8865|.33D2             xor   edx, edx                         ;0
004A8867|.E8 00FEFFFF      call    004A866C
004A886C\.C3               retn

一看代码,我就猜出,这应该是一个bit交换函数,1<->7,2<->3,4<->5,6<->0,将一个字节8位进行4组交换。

我们现在先来看看解码过程,在 0x004AE4C7 下一个断点,如下图所示:


先不要执行解码函数,如上图所示,可以看到解码前的字符串(在 OD 的数据区)。这里説明一下 delphi 的字符串格式:
struct String {
      DWORD refCount;
      DWORD length;
      char buffer;
};

其中当 refCount = 0xFFFFFFFF时,表示是常量字符串,不需要释放内存。大于或等于0时,表示是变量字符串,当为 0 时表示不再需要保留,可以释放内存了,而大于0时,表示还有引用,不能释放。

可以看到这个字符串的信息如下:
refCount = 0xFFFFFFFF
length= 0x00000029
buffer[] = "9 73 161 73 41 225 69 201 73 161 141 105 "

字符串通过空格分开的数字,数字的个数与 "unregistered"相同,其实这个字符串中的数字,就是字符的 ASCII 码通过前面的 swapbits() 交换后的 ASCII 码的十进制值。
并且顺序是反过来的,因此,我们只要把字符串后面两个数字去掉就变成 “REGISTERED”了,而去除字符串后面一截,只要修改长度就可以了。我们在内存中把长度改成 0x21,如下图所示:


如上图所示,改了长度,就是把所选部分(8字节)截掉了。现在我们执行解码函数,如下图所示:

可以看到,与预料的一样,解码后变成了 "REGISTERED"了,F9 运行,进入主界面。如下图所示,标题改过来了:



接下来我们修改前面 About 窗口留下的没有修改的红色“未注册/演示版/”等信息。
先在 DeDe Dark 中找到事件入口:miAboutClick(),地址为 0x004B13D0,如下图所示:

在 OD 中跳转到函数:

在函数内 0x004B1524 处下断点,然后点“About”按钮,就会断下来,如下图所示:

这里又是一个解码调用,解码后,得到一个字符串:


就是 about 窗口显示的“Unregistered /demo version/”,我们先要替换这条字符串。

因为这次需要修改字符串数据,所以需要先编码数据,因为是 bit 对称交换,编码/解码是同一个函数,所以我写了一个编码的代码,如下所示:
#include <iostream>
#include <string.h>

typedef unsigned char UCHAR;

const int MAX_LEN = 1000;

int encode(char * msg);
UCHAR swapBit(UCHAR ch, int n1, int n2);

int main(int argc, char** argv) {
      
      char msg[] = "Unregistered /demo version/";
      int len = encode(msg);
      printf("%d\n", len);
      
      char msg2[] = "Cracked by solly@52pojie.cn";
      int len2 = encode(msg2);
      printf("%d\n", len2);
      
      char msg3[] = "The trial version allow to load only the first %s records from table.";
      int len3 = encode(msg3);
      printf("%d\n", len3);

      char msg4[] = "Username: solly\nOrganization: 52pojie\nProductID: DBISAM%s1-CRKED-OK";
      int len4 = encode(msg4);
      printf("%d\n", len4);

      return 0;
}

int encode(char * msg) {
      int n = strlen(msg);
      char * rev_msg = strrev(msg);
      //printf("%s\n", rev_msg);
      UCHAR buff;
      char encodedMsg;
      int len = 0;
      for(int i=0; i<n; i++) {
                UCHAR ch = (UCHAR)rev_msg;
                //printf("%c ==>", ch);
                ch = swapBit(ch, 0, 6);
                ch = swapBit(ch, 1, 7);
                ch = swapBit(ch, 2, 3);
                ch = swapBit(ch, 4, 5);
                //printf("%02X ", ch);
                //printf("\n");
                buff = ch;
                if(len==MAX_LEN) {
                        break;
                }
      }
      buff = '\0';
      int pos = 0;
      for(int i=0; i<len; i++) {
                sprintf(&encodedMsg, "%d ", buff);
                pos = strlen(encodedMsg);
      }
      printf("%s\n", encodedMsg);
      
      return pos;
}

UCHAR swapBit(UCHAR ch, int n1, int n2) {
      UCHAR b1 = (n1!=0)?((ch>>n1) & 0x01):(ch & 0x01);
      UCHAR b2 = (n2!=0)?((ch>>n2) & 0x01):(ch & 0x01);
      //printf("b1 = %02X, b2 = %02X\n", b1, b2);
      if(n1 != 0) {
                ch = (UCHAR)((((ch>>n1) & 0xFE) | b2 ) << (n1)) | ((UCHAR)(ch<<(8-n1))>>(8-n1));
      } else {
                ch = (ch & 0xFE) | b2;
      }
      //printf(" ---> %02X ", ch);
      if(n2 != 0) {
                ch = (UCHAR)((((ch>>n2) & 0xFE) | b1 ) << (n2)) | ((UCHAR)(ch<<(8-n2))>>(8-n2));
      } else {
                ch = (ch & 0xFE) | b1;
      }
      //printf(" ---> %02X, ", ch);
      return ch;
}
执行上面代码,就可以得到我们需要的字符串,不过编码后的长度不要大于原长度(可以加上字符串后面的对齐用的留空),如下图所示:


进行二进制替换,替换后如下图所示:

包括长度也需要根据实际长度来修改,如上图中圆圈中所示数据。

接着,我们还要修改那个显示100条记录限制的字符串,如下图所示:

将代码生成的字符串替换上图中所选的数据,如下图所示:

执行解码后,得到我们想要的字符串:

显示用户名等信息。按 F9 运行,得到界面如下所示:

因为第1条是解码完成后才替换,所以还是显示原来的字符串,关闭about对话框,重新点“About”按钮,就可以再次打开 About 窗口,这次就正常了:

是不是有点成就了???

修改完成后,我们将这些修改保存到可执行程序,需要先将要保存的数据选中,如下图是开始位置:

结束位置如下图所示:

就是从地址 0x00469A9C 到 0x00469D9C 间的 0x300 字节,包括了我们刚才改动的3处数据。
在选中的数据上点右键菜单”复制到可执行文件“,然后保存为一个新的执行文件。

这个文件就是最终版本了,如下所示,最终效果图:



下面附上原版程序包:

最后附上,原版与修改版字节对比:


完成!!!

solly 发表于 2020-6-30 23:58

Hmily 发表于 2020-6-30 21:29
感觉还是idr导出map再导入od方便一些,那个100限制还我估计直接暴力搜常数来找了,卡死那个我遇到一般按f12 ...

IDR 解析好慢(其实通过代码地址值也可猜出哪些调用是delphi控件的,哪些作者写的),暂停会报Delphi全局异常,虽不会退出程序,但也恢复不了现场,这个大概是VCL框架消息循环处理方面的问题。

Hmily 发表于 2020-6-30 21:29

感觉还是idr导出map再导入od方便一些,那个100限制还我估计直接暴力搜常数来找了,卡死那个我遇到一般按f12暂停再f9就活过来了,这文章对离开说太简单,就不多加分了:lol

一丝风 发表于 2020-6-27 18:06

厉害厉害

doctrinist 发表于 2020-6-27 18:20

谢谢分享,很棒

阿基米德的微笑 发表于 2020-6-27 18:52

膜拜大佬,光截图就100多张,收藏研究研究

lxhwan100 发表于 2020-6-27 19:32

好详细啊,一步步的看,一步步的学

suifeng165 发表于 2020-6-27 20:56

纯技术贴,每个帖子都值得学习!!

xyl52p 发表于 2020-6-27 21:14

有水平的长篇,不过在下水平不照,看的云里雾里的。

pizazzboy 发表于 2020-6-27 21:48

楼主好耐心,收藏一个。谢谢分享。

qaz003 发表于 2020-6-28 01:29

哈哈,感觉是吃完饭之后尸气上来了,就懒得打字了。。。
{:301_988:}

wapjltb 发表于 2020-6-28 07:07

很好很详细的教程,请问楼主DeDe Dark这个工具哪里有下载?
页: [1] 2 3
查看完整版本: 某DBISAM文件型数据库管理小工具的试用版破解过程详细记录