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

标准的 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 指令即可。具体改为如下所示(修改的是最后一条指令):

跳过时间检查后,进入主界面,如下图所示:
按“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返回上一级函数,如下图所示:
具体代码如下:
01 02 03 04 05 06 07 08 09 10 11 12 | 004AC278 . 53 push ebx004AC279 . 8BD8 mov ebx, eax004AC27B . 80BB 78030000 00 cmp byte ptr [ebx+0x378], 0x0 ; 检查是否要显示 NAG004AC282 . 74 19 je short 004AC29D004AC284 . 8BC3 mov eax, ebx004AC286 . E8 9DE7FFFF call 004AAA28 ; beep()004AC28B . C683 78030000 00 mov byte ptr [ebx+0x378], 0x0004AC292 . 8B83 38020000 mov eax, dword ptr [ebx+0x238]004AC298 . 8B10 mov edx, dword ptr [eax]004AC29A . FF52 2C call dword ptr [edx+0x2C] ; 显示 NAG004AC29D > 5B pop ebx004AC29E . C3 retn |
可以看到,是通过判断 [ebx+0x378] 处的值来决定是否显示 NAG 窗口的(0:不显示;1:显示),对该处地址下一个硬件中断,如下图所示:



再一次 F9 运行,再次来到了显示 NAG 代码处,如下所示:


1 2 | 004AB29D C680 78030000 00 mov byte ptr [eax+0x378], 0x0 ; 是否显示 NAG, 1-显示,0-不显示004AB2A4 . C3 retn ; RET 用作跳转到 004AB2AC |







1 | 02484EE0 You may use shareware version for 30 days (TRIAL period). |


1 2 | 02487BF8 After that period has ended, to continue using this product you02487C38 have to purchase the Registered version. |

1 2 3 4 | 004B1513 |. 8B55 F8 mov edx, dword ptr [ebp-0x8]004B1516 |. 8B45 FC mov eax, dword ptr [ebp-0x4]004B1519 |. 8B80 FC010000 mov eax, dword ptr [eax+0x1FC]004B151F |. E8 90FAF6FF call <SetText()> |












现在我们来处理最多只显示 100 条记录的限制,如下图所示,当多于100条记录时,只显示100条:
首先在 DeDe Dark 中查看事件,找到 StringGrid 控件的刷新事件,如下图所示:
在 OD 中跳转到该事件,并下一个断点,断点我下在 0x004BD435,如下图所示:
点一下主界面的记录,变动一下活动的记录号,马上会中断下来,如下图所示:
上图中的 [ESI+0x1EC] 就是指的 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 返回,来到下面位置,可以看到,我们要找的地方找到了:
具体代码如下:
1 2 3 4 5 6 | 004AA6C3 . 8B83 EC010000 mov eax, dword ptr [ebx+0x1EC]004AA6C9 . 83B8 58010000 65 cmp dword ptr [eax+0x158], 0x65 ; 加上标题行,共 101 行004AA6D0 . 7E 0A jle short 004AA6DC ; 破解显示100条限制004AA6D2 . BA 65000000 mov edx, 0x65 ; 限制显示行数004AA6D7 . E8 FC90F9FF call 004437D8004AA6DC > A1 B44B4C00 mov eax, dword ptr [0x4C4BB4] |
在这里检查如果 rowCount 大于 0x65 则将 rowCount 改成 0x65。破解也简单,直接跳过修改即可,如下图所示:
具体修改如下:
1 2 | 004AA6C9 . 83B8 58010000 65 cmp dword ptr [eax+0x158], 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 掉,如下图所示:
具体改动如下:

回到软件,再次打开一个数据文件(orders.DAT),可以看到,可以显示101条以上的记录数据了,这个就是运气好吧:
该工具有一个查询过滤(Filter)功能,但是试用版没有生效,过滤条件没有执行,先在 DeDe Dark 中找到这个事件(miFilterClick)位置,如下图所示:
然后我们执行一下过滤功能,如下图所示:
但现在点“OK”没有用,在事件中下一个断点,如下图所示:
问题出在这里,这里总是返回 0,然后就跳过后面的循环过滤代码了,我们修改代码,强行返回 StringGrid 的行数即可。如下图所示:
正好下面有两行读取 StringGrid 的 RowCount 的代码,对照修改一下就可以了,具体修改如下所示:

再一次执行过滤功能,就可以用了:
不过该软件实现得不是太好,就是隐藏一下不符合要求的记录。
下面来处理软件导出数据的限制,导出时有些格式也会有100条记录的限制,同时会有一条水印(注释或标题)导出时不能修改。
如下图所示,先在 DeDe 找到事件入口:
在 OD 中跳转到该事件,并下一个断点(0x004B66F3),在软件中点“File"->"Save As",就会断下来:
F8 来到如下图所示位置,下两个断点,因为中间那条 call 0x0042F428 是显示导出窗口的,按F8执行会卡死(Delphi程序的通病)只能 F9 执行。
具体代码如下:

F9 后,弹出如下对话框:
先择一个保存的类型,按”确定“,如上图所示,我选择了 XML 格式。再次中断在前下的第2个断点,再 F8 执行到如下图所示位置:
这里的 al 就是我们在界面上选择的类型值,al==3 表示选择的第4项:XML格式。具体代码如下:

再一路F8,来到如下位置,这里开始处理数据保存了:
在这里按 F7 进入函数,具体代码如下:

进入 0x004B614C 函数后,会来到一个分支跳转处(switch),如下图所示,按不同的选择类型跳转:
跳转后,会来到如下位置:
这里就是处理 XML 格式导出的地方了。再次按 F7 进入 call 0x004B3364,一路 F8 来到如下位置,可以看到 100 条限制的指令:
上图中用红框框中的指令,就是其它类型导出时,限制100条记录的特征码,可以通过搜索这个特征码找到其它格式导出时的限制。
去除限制也简单,把 jg 指令 NOP 掉即可,如下图所示:
具体修改如下:

其它几种格式的限制指令位置如下,方便大家定位:

我们导出成 XML 文件后,打开看看,如下图所示:
选中的那一行就是所谓的水印,不过这条注释可以在软件的选项设置中修改,但由于是未注册版,禁止修改,强行附加并不允许修改设置,如下图所示:
所以,导出的文件中的总是会带有”Generated by DBISAM Viewer (Scalabium Software, http://www.scalabium.com/dbisam) “这一条水印信息,现在我们去除这个限制。
我们这次在 TfrmMain 的 FormCreate 事件中,找到这两个设置的初始化过程,如下图所示:
因为这个初始化是启动时处理的,我们先把前面对程序的修改保存下来,不然重启软件就没有了,如下图所示,选择右键菜单:
”复制到可执行文件“->”所有修改“,弹出如下确认框:
选择”全部复制“即可。
再右键”保存文件“。这样就把我们前面的修改保存下来了。
退出软件,重新用 OD 载入我们前面修改后保存的软件。
前面説了,这条水印其实可以在Options中修改,只是因为试用版限制修改了而矣,我们先在 DeDe 中找到设置的事件入口:
事件名为:miOptionsClick(),入口地址是 0x004B7D64。
在 OD 中跳转到该事件函数中,如下图所示,我可以看到初始化显示界面的代码,如下图所示:
但是再往后,却没有找到保存该设置的代码:
因此,我们需要重新补上这段代码,不然,还是无法修改并保存这个设置。
首先我们先找到一段类似的保存设置的代码,在这个代码基础上进行修改,如下图所示中选中的代码就是我们需要的:
具体代码如下所示:

我们就是要在这段代码的基础上来操作。我们需要在程序代码节的最后面,找一段空白处,依据上面代码,写入我们的代码,在 0x004BF660 处找到一处可以保存代码的空白空间。
同时,由于有代码重定位,我们在这个函数内要找一处不含重定位的5个字节代码写入跳转指令,跳转到我们的代码处,在函数后面保存数据最后处,找到了5字节,如下图所示位置:
代码位置如下:
1 2 3 4 | 004B8399 . 33C0 xor eax, eax004B839B ? 5A pop edx004B839C ? 59 pop ecx004B839D ? 59 pop ecx |

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | 004BF660 8B45 F8 mov eax, dword ptr [ebp-8]004BF663 8B80 4C020000 mov eax, dword ptr [eax+24C]004BF669 E8 8A9BF7FF call 004391F8004BF66E 8843 51 mov byte ptr [ebx+51], al004BF671 8D55 E8 lea edx, dword ptr [ebp-18]004BF674 8B45 F8 mov eax, dword ptr [ebp-8]004BF677 8B80 50020000 mov eax, dword ptr [eax+250]004BF67D E8 0219F6FF call 00420F84004BF682 8B55 E8 mov edx, dword ptr [ebp-18]004BF685 8D43 54 lea eax, dword ptr [ebx+54]004BF688 E8 AF43F4FF call 00403A3C004BF68D 90 nop004BF68E 33C0 xor eax, eax004BF690 5A pop edx004BF691 59 pop ecx004BF692 59 pop ecx004BF693 90 nop004BF694 ^ E9 058DFFFF jmp 004B839E004BF699 90 nop |

1 2 | 004B808E . 48 dec eax004B808F 0F85 F9750000 jnz 004BF68E |


1 2 3 4 5 6 7 8 | 004B7EFE . 8B45 F8 mov eax, dword ptr [ebp-8]004B7F01 . 8B80 4C020000 mov eax, dword ptr [eax+24C]004B7F07 B2 01 mov dl, 1004B7F09 . E8 0E90F6FF call 00420F1C004B7F0E . 8B45 F8 mov eax, dword ptr [ebp-8]004B7F11 . 8B80 50020000 mov eax, dword ptr [eax+250]004B7F17 B2 01 mov dl, 1004B7F19 . E8 FE8FF6FF call 00420F1C |




1 2 3 4 | 004AE53D . A1 884B4C00 mov eax, dword ptr [4C4B88]004AE542 EB 47 jmp short 004AE58B004AE544 90 nop004AE545 90 nop |



01 02 03 04 05 06 07 08 09 10 11 12 13 14 | 004AE4C7 . FFB5 60FEFFFF push dword ptr [ebp-1A0] ; "Direct DBISAM table reading"004AE4CD . 68 24F74A00 push 004AF724 ; (004AE4D2 . 8D95 6CFEFFFF lea edx, dword ptr [ebp-194]004AE4D8 . A1 1C4D4C00 mov eax, dword ptr [4C4D1C]004AE4DD . 8B00 mov eax, dword ptr [eax]004AE4DF . E8 8CA3FFFF call <DecodeString()> ; Decode004AE4E4 . FFB5 6CFEFFFF push dword ptr [ebp-194]004AE4EA . 68 FCF64A00 push 004AF6FC ; )004AE4EF . 8D85 64FEFFFF lea eax, dword ptr [ebp-19C]004AE4F5 . BA 04000000 mov edx, 4004AE4FA . E8 2558F5FF call 00403D24004AE4FF . 8B95 64FEFFFF mov edx, dword ptr [ebp-19C]004AE505 . 8B45 FC mov eax, dword ptr [ebp-4]004AE508 . E8 A72AF7FF call <SetText()> ; setText("(UNREGISTERED)") |



01 02 03 04 05 06 07 08 09 10 11 12 13 | 004A8848 /$ B1 01 mov cl, 1004A884A |. B2 07 mov dl, 7004A884C |. E8 1BFEFFFF call 004A866C ; swapbit()004A8851 |. B1 02 mov cl, 2004A8853 |. B2 03 mov dl, 3004A8855 |. E8 12FEFFFF call 004A866C004A885A |. B1 04 mov cl, 4004A885C |. B2 05 mov dl, 5004A885E |. E8 09FEFFFF call 004A866C004A8863 |. B1 06 mov cl, 6004A8865 |. 33D2 xor edx, edx ; 0004A8867 |. E8 00FEFFFF call 004A866C004A886C \. C3 retn |

1 2 3 4 5 | struct String { DWORD refCount; DWORD length; char buffer[length];}; |
1 2 3 | refCount = 0xFFFFFFFFlength = 0x00000029buffer[] = "9 73 161 73 41 225 69 201 73 161 141 105 " |







01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | #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 [url=mailto:solly@52pojie.cn]solly@52pojie.cn[/url]"; 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[MAX_LEN]; char encodedMsg[MAX_LEN*4]; int len = 0; for(int i=0; i<n; i++) { UCHAR ch = (UCHAR)rev_msg[i]; //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[len++] = ch; if(len==MAX_LEN) { break; } } buff[len] = '\0'; int pos = 0; for(int i=0; i<len; i++) { sprintf(&encodedMsg[pos], "%d ", buff[i]); 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;} |










DBISAMViewer_original.rar (609.6 KB, 下载次数: 18)
patch_bytes.txt (6.53 KB, 下载次数: 4)来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69953058/viewspace-3146136/ ,如需转载,请注明出处,否则将追究法律责任。