OD复制BUG分析和修正

作者: nbw   
nbw.07cn.com
For NE365

使用OD,从内存区复制内存数据的时候,有时候无法将所有的数据都复制到剪贴板,应该是od一个bug,本文说以下该bug的产生原因和解决方法。最后给出一个处理补丁。
我这里处理的是 OllyDbg v1.0 2005.3 修正版。

用一个OD1附加另外一个OD2,在OD2里面复制内存区内容。

拦截剪贴板操作函数,到:

0044AE96         |.  53                push ebx                                      ; /hData
0044AE97         |.  6A 0D             push 0D                                       ; |Format = CF_UNICODETEXT
0044AE99         |.  E8 E4460600       call <jmp.&USER32.SetClipboardData>           ; \SetClipboardData

其中 push ebx传入数据缓冲区指针。
上面有函数处理最终输入剪贴板的数据:
0044AE6B         |.  52                push edx                                      ; /Arg3
0044AE6C         |.  50                push eax                                      ; |Arg2
0044AE6D         |.  8B45 0C           mov eax,dword ptr ss:[ebp+C]                  ; |
0044AE70         |.  50                push eax                                      ; |Arg1
0044AE71         |.  E8 A2B7FFFF       call OLLYDBG.00446618                         ; \OLLYDBG.00446618

上面的call OLLYDBG.00446618将用户需要复制的数据转换成Unicode编码,然后填入输出缓冲区,这是个循环,不能一次性转换完毕,需要一块一块进行。关键地方如下:
将复制的数据转换为UNICODE编码:

00446A1C         |> \68 00020000   ||push 200                                   ; /WideBufSize = 200 (512.)
00446A21         |.  8D95 78FAFFFF ||lea edx,dword ptr ss:[ebp-588]             ; |
00446A27         |.  52            ||push edx                                   ; |WideCharBuf
00446A28         |.  53            ||push ebx                                   ; |StringSize
00446A29         |.  8D8D 78FDFFFF ||lea ecx,dword ptr ss:[ebp-288]             ; |
00446A2F         |.  51            ||push ecx                                   ; |StringToMap
00446A30         |.  6A 01         ||push 1                                     ; |Options = MB_PRECOMPOSED
00446A32         |.  6A 00         ||push 0                                     ; |CodePage = CP_ACP
00446A34         |.  E8 1D870600   ||call <jmp.&KERNEL32.MultiByteToWideChar>   ; \MultiByteToWideChar

往缓冲区填充转换出来的UNICODE数据:

00446CA7         |.  50            ||push eax                                   ; /数据长度
00446CA8         |.  52            ||push edx                                   ; |原始数据地址
00446CA9         |.  8B4D D8       ||mov ecx,dword ptr ss:[ebp-28]              ; |
00446CAC         |.  03C9          ||add ecx,ecx                                ; |
00446CAE         |.  034D CC       ||add ecx,dword ptr ss:[ebp-34]              ; |
00446CB1         |.  51            ||push ecx                                   ; |缓冲区地址
00446CB2         |.  E8 79C80500   ||call OLLYDBG.004A3530                      ; \填充数据

填写每行后面的回车换行符号:
00446CFE         |.  66:C70441 0D0>|mov word ptr ds:[ecx+eax*2],0D              ;  填充换行符号
00446D04         |.  FF45 D8       |inc dword ptr ss:[ebp-28]
00446D07         |.  8B55 CC       |mov edx,dword ptr ss:[ebp-34]
00446D0A         |.  8B4D D8       |mov ecx,dword ptr ss:[ebp-28]
00446D0D         |.  66:C7044A 0A0>|mov word ptr ds:[edx+ecx*2],0A              ;  填写回车符号

上面的 MultiByteToWideChar 是关键地方。这个函数有时候转换出来的数据中会存在连续2个字节的00。

比如现在要从内存模块复制以下2行数据:

004029F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 CC  ...............
00402A00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

以上数据转换成的unicode编码如下:

005F2320   30 00 30 00 34 00 30 00 32 00 39 00 46 00 30 00  0.0.4.0.2.9.F.0.
005F2330   20 00 20 00 30 00 30 00 20 00 30 00 30 00 20 00   . .0.0. .0.0. .
005F2340   30 00 30 00 20 00 30 00 30 00 20 00 30 00 30 00  0.0. .0.0. .0.0.
005F2350   20 00 30 00 30 00 20 00 30 00 30 00 20 00 30 00   .0.0. .0.0. .0.
005F2360   30 00 20 00 30 00 30 00 20 00 30 00 30 00 20 00  0. .0.0. .0.0. .
005F2370   30 00 30 00 20 00 30 00 30 00 20 00 30 00 30 00  0.0. .0.0. .0.0.
005F2380   20 00 30 00 30 00 20 00 30 00 30 00 20 00 43 00   .0.0. .0.0. .C.
005F2390   43 00 20 00 20 00 2E 00 2E 00 2E 00 2E 00 2E 00  C. . ...........
005F23A0   2E 00 2E 00 2E 00 2E 00 2E 00 2E 00 2E 00 2E 00  ................
005F23B0   2E 00 2E 00 00 00 0D 00 0A 00 30 00 30 00 34 00  ..........0.0.4.
005F23C0   30 00 32 00 41 00 30 00 30 00 20 00 20 00 30 00  0.2.A.0.0. . .0.
005F23D0   30 00 20 00 30 00 30 00 20 00 30 00 30 00 20 00  0. .0.0. .0.0. .
005F23E0   30 00 30 00 20 00 30 00 30 00 20 00 30 00 30 00  0.0. .0.0. .0.0.
005F23F0   20 00 30 00 30 00 20 00 30 00 30 00 20 00 30 00   .0.0. .0.0. .0.
005F2400   30 00 20 00 30 00 30 00 20 00 30 00 30 00 20 00  0. .0.0. .0.0. .
005F2410   30 00 30 00 20 00 30 00 30 00 20 00 30 00 30 00  0.0. .0.0. .0.0.
005F2420   20 00 30 00 30 00 20 00 30 00 30 00 20 00 20 00   .0.0. .0.0. . .
005F2430   2E 00 2E 00 2E 00 2E 00 2E 00 2E 00 2E 00 2E 00  ................
005F2440   2E 00 2E 00 2E 00 2E 00 2E 00 2E 00 2E 00 2E 00  ................
005F2450   0D 00 0A 00 0D 00 0A 00 00 00 00 00 00 00 00 00  ................

可以看到005F23B0那行:
005F23B0   2E 00 2E 00 00 00 0D 00 0A 00 30 00 30 00 34 00  ..........0.0.4.

前面的 2E 00 2E 00 是第一行倒数第2个和倒数第3个数据,也就是被004029F0那行最后的2个小数点,而2E 00后面的连续2个00就是因为要复制的数据CC被OD转换成的字符无法被转换成Unicode而留下来的,再往后0D 00 0A 00 是回车换行。
看起来有点混乱,主要是我表达不太清楚。
换句话说,要复制的数据:
004029F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 CC  ...............@
左边的数据被OD在右边显示成相应的ASCII码,CC被显示成一个字符而不是小数点,假定是 @ ,当然实际上不是,你可以去OD看一下。然后复制的时候,那个@也被包含了进去,但是转换unicode码的时候被转换成了连续的2个00。

因为剪贴板操作数据仅仅根据的缓冲区句柄,是数据中间出现了2个00,从而导致使00后面的数据被视而不见。
有很多办法可以处理这个问题,我这里修正MultiByteToWideChar的输出数据。


修正MultiByteToWideChar函数

该函数将输入的字符串转换为Unicode编码。如果输入的数据中含有大于0x80的字节,可能会导致输出数据的最后2个字节都为00,从而截断需要复制的数据。

分析:

    名称     RVA     OA        尺寸D   可写否
   .text  000b0000  000af600  -2048      否
   .data  0010b000  00109e00  -253440      可
    .tls  0010c000  000cd000  -3584      可
  .rdata  0010d000  000cd200  -3584      否
  .idata  0010f000  000ce400   -512      否
  .edata  00111000  000d0200  -3072      否
   .rsrc  00145934  00103f34    212      否
  .reloc  00152000  00110200   -512      否
有效剩余空间(字节D)为: 212

选定  .rsrc 段 00145954 处
即:
00545954   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00545964   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

函数调用如下:

;ecx -> 输入缓冲区
;edx -> 输出缓冲区
;ebx -> 数据长度

00446A1C         |> \68 00020000       ||push 200                                    ; /WideBufSize = 200 (512.)
00446A21         |.  8D95 78FAFFFF     ||lea edx,dword ptr ss:[ebp-588]              ; |
00446A27         |.  52                ||push edx                                    ; |WideCharBuf
00446A28         |.  53                ||push ebx                                    ; |StringSize
00446A29         |.  8D8D 78FDFFFF     ||lea ecx,dword ptr ss:[ebp-288]              ; |
00446A2F         |.  51                ||push ecx                                    ; |StringToMap
00446A30         |.  6A 01             ||push 1                                      ; |Options = MB_PRECOMPOSED
00446A32         |.  6A 00             ||push 0                                      ; |CodePage = CP_ACP
00446A34         |.  E8 1D870600       ||call <jmp.&KERNEL32.MultiByteToWideChar>    ; \MultiByteToWideChar
                                                                                     ;:00446A34 E81D870600      Call 004AF156
00446A39         |.  8BD8              ||mov ebx,eax

下面进行修正:

00446A1C         |> \68 00020000       ||push 200
修改为 jmp 00545954

在00545954处添加代码:

    lea edx,dword ptr ss:[ebp-588]              ; |
    lea ecx,dword ptr ss:[ebp-288]              ; |
    push ecx                                    ;保存输入缓冲区地址
    push edx                                    ;保存输出缓冲区地址

    push 200                                    ; /WideBufSize = 200 (512.)
    push edx                                    ; |WideCharBuf
    push ebx                                    ; |StringSize
    push ecx                                    ; |StringToMap
    push 1                                      ; |Options = MB_PRECOMPOSED
    push 0                                      ; |CodePage = CP_ACP
    call <jmp.&KERNEL32.MultiByteToWideChar>    ; \MultiByteToWideChar

    pop edx                                     ;输出缓冲区
    pop ecx                                     ;输入缓冲区
    mov ebx, eax                                ;获取输出缓冲区长度,这里采用上面API函数返回值不采用原来的输入长度,因为有时候不一样

    add   ebx, ebx
    add   edx, ebx
    sub   edx, 2                                ;定位到输出缓冲区倒数第2个字节
    movzx ebx, word ptr [edx]
    cmp   ebx , 0
    je    @F
    jmp   00446A39                              ;返回
@@:
    mov   byte ptr[edx], 01                     ;如果最后是连续2个00字节,那么写入01,正确性不管了,如果愿意你可以自己修正 :)
    jmp   00446A39


关于MultiByteToWideChar产生连续2个字节的00情况,是因为该函数计算数据的unicode码主要是根据查表,如果数据小于0x80,那么就不会产生连续2个字节的00,如果大于0x80,会导致数据前移,使得最后2个字节是00,如果再大(具体多少没研究),就不会有00了,不过产生的数据应该不对。有兴趣的朋友可以看看这个函数,并不复杂。
按道理网上或者什么地方应该有关于这个情况的解释,哪位晓得不妨给我说以下。

修改完毕,对同一段内存,测试以下。

修改前的可以复制:
00402230  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 56  ...............V
00402240  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 66  ...............f
00402250  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78  ...............x
00402260  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 88  ...............

修改后的版本可以复制:
00402250  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 56  ...............V
00402260  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 66  ...............f
00402270  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78  ...............x
00402280  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 88  ...............
00402290  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 98  ...............
004022A0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F8  ...............
004022B0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

我不太敢改调试器之类的东西,怕出了问题影响大家工作,所以如果发现问题请给我说以下,我好改正。

多谢!