【文章标题】: CAJViewer7.0中OCR功能的提取
【文章作者】: yfliu
【作者邮箱】: 80600414@qq.com
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
CAJViewer7.0中的文字识别功能相当强大,识别准确率很高,但该功能只局限在CAJViewer7.0能打开的有限的几种格式(nh,kdh,ceb,pdf)中,并不能直接用于识别图片。本文描述了一种方法,将OCR功能从CAJViewer7.0中剥离出来,独成一体。
工具:ida,syser debug,filemon, CAJViewer 7.0.1.sfx
一.为什么开始动手做
一直认为caj的ocr功能很强大,以前一直没做这件事是因为我以为他是同方自己开发的系统,和caj融为一体,提取难度较大。
今天偶然发现在caj的目录下有个ocr目录,里面有一个exe和若干dll文件,便猜想ocr功能只是caj的一个插件,既然是插件,难度就小多了。
二.首先想到的:ocr以dll的方式向caj提供服务。
用ida检查导入表,有LoadLibraryA,查看对该函数的引用,从加载的文件名和代码来看,并不是在加载ocr相关的代码,全是加载caj的文件。排除了这种可能
三.ocr以单独的进程完成识别,然后将识别结果以某种方式传回caj。
观察到:每当我使用ocr功能时,在任务管理器中总会多出一个THOCRecog的进程,然后很快地又消失了。
四.
既然是通过单独的进程来处理,完成后进程消失,那么程序得调用CreateProcess了。
打开ida发现了很多对CreateProcessA的调用
对于在ida中的每一个对CreateProcessA的调用:1.如果根据lpCommandLine或lpApplicationName不能判断出在创建ocr相关的进程。则2.记下它的虚拟地址,在syser中下断点。如:
.text:004295FC add esp, 28h
.text:004295FF lea eax, [ebp+3DCh+hObject]
.text:00429602 push eax ; lpProcessInformation
.text:00429603 lea eax, [ebp+3DCh+StartupInfo]
.text:00429606 push eax ; lpStartupInfo
.text:00429607 push esi ; lpCurrentDirectory
.text:00429608 push esi ; lpEnvironment
.text:00429609 push 8000000h ; dwCreationFlags
.text:0042960E push esi ; bInheritHandles
.text:0042960F push esi ; lpThreadAttributes
.text:00429610 push esi ; lpProcessAttributes
.text:00429611 lea eax, [ebp+3DCh+CommandLine]
.text:00429614 push eax ; lpCommandLine
.text:00429615 push esi ; lpApplicationName
.text:00429616 call ds:CreateProcessA ;
根据上下文是不能判断是否在创建ocr相关进程的。
在syser中切换到caj的空间,对429616下断点,然后查看lpCommandLine,即eax
在最上面就是内存窗口,在右上面显示就是lpCommandLine的值,可以看出这个CreateProcess函数在创建ocr相关进程。
还可以知道,caj根据用户选择的区域,临时生成了一张bmp图像,然后传给ocr处理。
.text:00429615 push esi ; lpApplicationName
.text:00429616 call ds:CreateProcessA ;
.text:0042961C test eax, eax
.text:0042961E jnz short loc_42962F
.text:0042961E
.text:00429620 push offset ValueName
.text:00429625 mov ecx, edi
.text:00429627 call ds:ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(char const *)
.text:0042962D jmp short loc_42965E
.text:0042962D
.text:0042962F ; ---------------------------------------------------------------------------
.text:0042962F
.text:0042962F loc_42962F: ; CODE XREF: sub_429581+9D j
.text:0042962F push 0FFFFFFFFh ; dwMilliseconds
.text:00429631 push [ebp+3DCh+hObject] ; hHandle
.text:00429634 call ds:WaitForSingleObject
.text:0042963A lea eax, [ebp+3DCh+ExitCode]
.text:0042963D push eax ; lpExitCode
.text:0042963E push [ebp+3DCh+hObject] ; hProcess
.text:00429641 call ds:GetExitCodeProcess
.text:00429647 push [ebp+3DCh+hObject] ; hObject
.text:0042964A mov esi, ds:CloseHandle
.text:00429650 call esi ; CloseHandle
.text:00429652 push dword ptr [ebp-38h] ; hObject
.text:00429655 call esi ; CloseHandle
.text:00429657 push edi
.text:00429658 call sub_4294E6
继续观察CreateProcess后的操作:程序将新创建的句柄传递给WaitForSingleObject,这个函数等待新创建的进程结束;然后程序调用一个过程sub_4294E6。
这个过程主要是对系统的剪切板操作:
.text:004294E6 sub_4294E6 proc near ; CODE XREF: sub_429581+D7 p
.text:004294E6 mov eax, offset loc_450FAF
.text:004294EB call __EH_prolog
.text:004294EB
.text:004294F0 push ecx
.text:004294F1 and dword ptr [ebp-10h], 0
.text:004294F5 push esi
.text:004294F6 lea ecx, [ebp-10h]
.text:004294F9 call ds:ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:004294FF and dword ptr [ebp-4], 0
.text:00429503 push offset szFormat ; "THOCR_FMT"
.text:00429508 call ds:RegisterClipboardFormatA
.text:0042950E push 0 ; hWndNewOwner
.text:00429510 mov esi, eax
.text:00429512 call ds:OpenClipboard
.text:00429518 test eax, eax
.text:0042951A jnz short loc_42952C
.text:0042951A
.text:0042951C mov ecx, [ebp+8]
.text:0042951F push offset ValueName
.text:00429524 call ds:ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(char const *)
.text:0042952A jmp short loc_429568
.text:0042952A
.text:0042952C ; ---------------------------------------------------------------------------
.text:0042952C
.text:0042952C loc_42952C: ; CODE XREF: sub_4294E6+34 j
.text:0042952C push esi ; uFormat
.text:0042952D call ds:GetClipboardData
.text:00429533 mov esi, eax
.text:00429535 test esi, esi
.text:00429537 jz short loc_429555
.text:00429537
.text:00429539 push esi ; hMem
.text:0042953A call ds:GlobalLock
.text:00429540 test eax, eax
.text:00429542 jz short loc_429555
.text:00429542
.text:00429544 push eax
.text:00429545 lea ecx, [ebp-10h]
.text:00429548 call ds:ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::operator=(char const *)
.text:0042954E push esi ; hMem
.text:0042954F call ds:GlobalUnlock
.text:0042954F
.text:00429555
.text:00429555 loc_429555: ; CODE XREF: sub_4294E6+51 j
.text:00429555 ; sub_4294E6+5C j
.text:00429555 call ds:CloseClipboard
小结一下caj的处理过程:
1.caj通过CreateProcessA创建一个进程,THOCRecog
2.caj调用WaitForSingleObject,等待THOCRecog处理完成,THOCRecog将处理结果放在系统剪切板中
3.caj调用GetClipboardData从系统剪切板获得数据
4.caj通过一些mfc的字符串函数解析第3步取得的数据
五.明白了caj的处理过程,怎么利用?
1.给THOCRecog传递一个图片的路径
2.待THOCRecog处理完后从系统剪切板取回数据。
还有两个问题:
1. THOCRecog会判断它的父进程是否为caj吗?如果不是caj调用它,它还能工作吗?
2.系统剪切板中数据的格式的解析方式是什么?
六 .解决上面两个问题------ THOCRecog的运行流程
在命令行方式下直接启动THOCRecog,后跟一个图片文件路径,通过filemon发现THOCRecog同样会在图片路径创建一些文件,但是很快又删除了,怀疑这就是THOCRecog处理的结果。
如果我跳开THOCRecog中的删除文件的函数,就可以查看这些文件的内容。
找到THOCRecog中对DeleteFileA引用的部分。
.text:00401313 loc_401313: ; CODE XREF: sub_4010B0+1FD j
.text:00401313 ; sub_4010B0+207 j
.text:00401313 mov edx, [ebp+lpFileName]
.text:00401316 mov esi, ds:DeleteFileA
.text:0040131C push edx ; lpFileName
.text:0040131D call esi ; DeleteFileA
将00401313处的代码改为retn。
观察图片目录,多了4个文件,其中一个和图片同名的txt中存放了识别的结果。
上面的处理达到了效果,但是显得很暴力。
分析该DeleteFileA 所在的函数,可以推测THOCRecog大概的过程:
1.根据传入bmp文件路径,THOCRecog分析该文件,将分析结果存在图片目录,与图片文件同名的txt文件中。
2. THOCRecog读取该txt文件并把读取的内存规格为与caj约定的格式。
3. THOCRecog将已经规格化的内容存在系统剪切板中。
4. THOCRecog删除txt等图片目录下的4个文件。结束进程。
七.怎么利用
我对mfc不熟悉,没法逆向解析剪切板数据的相关过程,所以采取下面方法
.text:00401207 test eax, eax
.text:00401209 jz short loc_401214
.text:00401209
.text:0040120B mov byte ptr [ebp+var_4], 9
.text:0040120F jmp loc_40135C
.text:0040120F
.text:00401214 ; ---------------------------------------------------------------------------
.text:00401214
.text:00401214 loc_401214: ; CODE XREF: sub_4010B0+159 j
.text:00401214 call sub_401710
将.text:00401209 jz short loc_401214改为nop可以直接跳过上面的4个过程
然后用自己的程序读取数据,删除文件…
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2008年05月16日 11:50:48
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!