首页
论坛
课程
招聘
[原创]ETU-Dasm v2.35 ALPHA菜单“资源”命令Bugfix
2012-8-7 08:20 2948

[原创]ETU-Dasm v2.35 ALPHA菜单“资源”命令Bugfix

2012-8-7 08:20
2948
  解决了chixiaojie在帖子“ETU-Dasm v2.35 ALPHA 中文乱码问题!(替代 W32Dasm 的另一重量级静态反汇编工具)”中提到的问题。感谢chixiaojie提供这个工具,同时也感谢yjd333提供了"Hacker's Disassembler 1.06"的信息。
  chixiaojie曾在UpK的帖子eXeScope, XNResourceEditor & IDR 之Form中文显示补丁”中提到能否解决这个问题,因为当时在整理“Restorator 2007注册的RSA算法分析及RCData中文处理补丁”(如果对RSA算法感兴趣,强烈建议读读这个帖子。),就下载了英文原版初步试用了一下,发现这个软件确实不错,便回复到时看看。
  英文版改好后的效果(以打开Windows XP Pro. SP3 CHS的记事本为例):

  "Hacker's Disassembler 1.06"最新的版本为"HDasm 1.06 rev C (Jan 29 2007, 592 kb)",用Borland C++ 2002编写。英文版的HDasm能够正确处理菜单资源里的中文字符:

  但是字符串资源的处理还是有问题(对比上面修改过的ETU-Dasm):

  总的说来,HDasm还是不错滴,用户多一个选择。基本上与W32Dasm(它同样不能正确处理中文的菜单参考和字符串参考)相似。个人感觉ETU-Dasm要强于HDasm和W32Dasm。比如,反汇编代码的地址常量、调用和跳转的目标地址都用不同的颜色区分,并且提供右键菜单。当鼠标移到地址上时会出现一个信息框,显示目标地址附近的内容,可使用Tab键在数据模式和代码模式间切换,这个功能显得非常的人性和智能:



  ETU-DASM从2001年1月开始开发,2005年12月发布了最后这个版本,编程采用"Borland C++ 4.02"。古董级的东西,直接导致象"EMS Source Rescuer"和DelphiDecompiler等工具无法使用,因为那时它的"Form"还在标准的Dialog资源里面,没有在RCData里面。
  顺便说一下,有个工具"Delphi and C++Builder Decompiler" v3.42或者v8.11,是“山寨”"EMS Source Rescuer"的,它运行时会在%TEMP%文件夹下生成一个隐藏的临时文件,实际上就是REVENGE小组Jacky对"EMS Source Rescuer"的一个破解。

  下面大致说一下解决问题的大致过程,使用工具:OD和Hiew。
  问题的原因:菜单资源和字符串资源里的文本是unicode编码的,每个字符是定长的,用一个字表示。ETU-DASM在处理时,简单地按单字节字符处理,即取每个unicode字符的低字节,这在纯西文时没有问题,但在象中文等多字节字符时其效果是将两个字合并为一个字,结果必然就不可识别。所以,应该算是一个Bug。
  解决方法:在显示输出前,通过Windows的API函数WideCharToMultiByte将unicode字符转换到ANSI字符。但是ETU-DASM的输入表中缺少这个函数,我们得为它添加一个。
  用代码的方法来实现基本上是LoadLibraryA和GetProcAddress的组合,用完后还需要FreeLibrary,比较繁琐。这里我们采用修改输入表的方法,让操作系统在载入ETU-DASM时为我们取得函数WideCharToMultiByte的实际入口地址。
  在输入表里添加函数WideCharToMultiByte可以用一些工具,比如Stud_PE,但是它会整出一大堆不需要的东西,而且还增加一个区段。这里我们用手工来修改,也好复习一下输入表的相关概念。

  在一个PE文件中,当调用另一个模块中的函数(比如USER32.DLL中的GetMessage)时,编译器产生的CALL指令通常不会直接将控制传到DLL里的这个函数(见下图),而是将CALL指令转到同是本代码区段的一条指令:"JMP DWORD PTR [XXXXXXXX]"。

  JMP指令间接地用到输入表中一个DWORD变量,这个变量为操作系统中该函数的真实入口地址。这样做的好处是,操作系统在载入程序时不需要去调整每一个调用这个函数的CALL指令地址,PE loader唯一要做的事情就是将目标函数的正确地址填入这个DWORD内,CALL指令的地址不需修改。
  输入表(Import Table)的一个作用就是为PE loader提供进行Thunk(形实替换)的信息。输入表的开始是多个IMAGE_IMPORT_DESCRIPTOR的列表,每个PE文件需要隐式链接的DLL对应一个IMAGE_IMPORT_DESCRIPTOR,没有字段用来表示IMAGE_IMPORT_DESCRIPTOR的个数,而是将最后一个IMAGE_IMPORT_DESCRIPTOR的所有字段置NULL(0)来表示列表的结束。
  IMAGE_IMPORT_DESCRIPTOR的结构为:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;      // 该union的命名非常糟糕。0 表示输入描述表结束。
        DWORD   OriginalFirstThunk;   // 含有输入名表(Import Name Table - INT - 有时又叫hint-name表)的RVA,或原始未联编IAT的RVA(PIMAGE_THUNK_DATA)
    };
    DWORD   TimeDateStamp;            // 0 表示没有联编到输入DLL。
                                      // -1 新式联编(new BIND)。
                                      // 旧式联编(Old BIND)为输入DLL的时间戳(自1/1/1970 GMT以来的秒数)

    DWORD   ForwarderChain;           // 适用于旧式联编,为第一个转发API的索引。无转发为-1。
    DWORD   Name;                     // 输入DLL名称ASCII字符串的RVA。
    DWORD   FirstThunk;               // 含有输入地址表(Import Address Table - IAT)的RVA。若已联编IAT内为实际地址。
} IMAGE_IMPORT_DESCRIPTOR;


  注意它的大小为0x14字节,每个IMAGE_IMPORT_DESCRIPTOR中OriginalFirstThunk和FirstThunk典型地指向两个基本上相同的IMAGE_THUNK_DATA结构组成的表,这两个表同样没有字段来表明它的大小,用一个DWORD的NULL来结束表。
  IMAGE_THUNK_DATA的结构为:
typedef struct _IMAGE_THUNK_DATA32 {
    union {
        PBYTE  ForwarderString;                // 指向转发字符串的RVA。
        PDWORD Function;                       // 输入函数的内存地址
        DWORD Ordinal;                         // 输入API的Ordinal值(序号)
        PIMAGE_IMPORT_BY_NAME  AddressOfData;  // 指向含输入API名的IMAGE_IMPORT_BY_NAME结构的RVA。
    } u1;
} IMAGE_THUNK_DATA32;

  IMAGE_IMPORT_BY_NAME结构很简单:
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;            // 为输入函数的Ordinal值。正如它自身的意思,仅仅是"Hint",不象NE文件,其值并不要求准确。
    BYTE    Name[?];         // 函数名字符串 + NULL(0)。
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;


  为什么会有两个并行的IMAGE_THUNK_DATA列表?
  这里有一个联编(Binding)的问题,我们不深入讨论它,会扯得很远。简单地说,OriginalFirstThunk指向的INT是查找输入函数的唯一依据,它永远不会被修改。
  在联编的PE文件中,比如使用Bind程序,或者Windows安装程序的BindImage操作,FirstThunk指向的IAT已经含有输入函数的实际地址,PE loader不需要再进行Thunk。
  在未联编的情况下,FirstThunk字段指向的IMAGE_THUNK_DATA列表会被PE loader重写,loader重复每一个表项:用它找到的函数的实际地址覆盖指向IMAGE_IMPORT_BY_NAME的指针。指令JMP DWORD PTR [XXXXXXXX]中的[XXXXXXXX]部分实际上指向FirstThunk列表中的一项,每一项都是loader Thunk的结果。正因为FirstThunk列表中被loader重写的指针为每个输入函数的实际地址,所以被称为输入地址表(IAT)。
  INT对执行文件的加载不是必须的,但是没有它执行文件就不能被联编。Microsoft的连接器似乎总会生成INT,而Borland早期的连接器(TLINK)一直都没有生成过INT。

  这段文字比较抽象哈,我们现在用实例来理解。用Hiew打开"Windows XP Pro. SP3 CHS"的记事本NOTEPAD.EXE。
  NOTEPAD.EXE是已联编的程序,在Hiew中按Enter切换到HEX模式,再F8、F10调出IMAGE_OPTIONAL_HEADER中的DataDirectory(为IMAGE_DATA_DIRECTORY结构的列表):
    Name       RVA      Size
Export       00000000 00000000
Import       00007604 000000C8
Resource     0000B000 00007F20
Exception    00000000 00000000
Security     00000000 00000000
Fixups       00000000 00000000
Debug        00001350 0000001C
Description  00000000 00000000
MIPS GP      00000000 00000000
TLS          00000000 00000000
Load config  000018A8 00000040
Bound Import 00000250 000000D0
Import Table 00001000 00000348
Delay Import 00000000 00000000
COM Runtime  00000000 00000000
(reserved)   00000000 00000000

  IMAGE_DATA_DIRECTORY的结构:
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

  在"Import"项按Enter来到输入表:
01007600:              90 79 00 00.FF FF FF FF.FF FF FF FF
01007610:  AC 7A 00 00.C4 12 00 00
...

  这是第一个IMAGE_IMPORT_DESCRIPTOR,OriginalFirstThunk=00007990,TimeDateStamp=FFFFFFFF,ForwarderChain=FFFFFFFF,Name=00007AAC,FirstThunk=000012C4:
01007990:  7A 7A 00 00.5E 7A 00 00.9E 7A 00 00.50 7A 00 00
...
01007A10:  00 00 00 00.04 00 43 6F.6D 6D 44 6C.67 45 78 74
...
01007A70:  61 63 65 54.65 78 74 57.00 00 0F 00.50 61 67 65  aceTextW    Page
01007A80:  53 65 74 75.70 44 6C 67.57 00 0A 00.47 65 74 4F  SetupDlgW   GetO
...
01007AA0:  50 72 69 6E.74 44 6C 67.45 78 57 00.63 6F 6D 64  PrintDlgExW comd
01007AB0:  6C 67 33 32.2E 64 6C 6C.00 00 03 01.53 68 65 6C  lg32.dll    Shel
...
010012C0:  00 00 00 00.06 49 34 76.CE 85 33 76.84 9D 34 76
...

  从Name处01007AAC看到,是输入comdlg32.dll中的函数,为新式联编、无转发。OriginalFirstThunk的第一项为00007A7A,其IMAGE_IMPORT_BY_NAME中的Hint=000F,Name[]="PageSetupDlgW"。FirstThunk的第一项为76344906,就是函数PageSetupDlgW的实际入口地址。

  下面开始分析我们的目标程序etu_v235.exe,先看看JMP DWORD PTR [XXXXXXXX]的例子。例如,00420896处调用KERNEL32.dll里的函数SetFilePointer:
...
00420896: E8EFC60600     call   SetFilePointer     ; Hiew中ALT+F11显示为:call   00048CF8A
...

  00048CF8A处为一系列JMP DWORD PTR [XXXXXXXX]的开始:
...
0048CF89: C3             retn
0048CF8A: FF25A0004D00   jmp    SetFilePointer              ; jmp   d,[0004D00A0]
0048CF90: FF25A4004D00   jmp    VirtualQuery                ; jmp   d,[0004D00A4]
0048CF96: FF25A8004D00   jmp    WriteFile                   ; jmp   d,[0004D00A8]
0048CF9C: FF25AC004D00   jmp    InitializeCriticalSection   ; jmp   d,[0004D00AC]
0048CFA2: FF25B0004D00   jmp    CreateMutexA                ; jmp   d,[0004D00B0]
0048CFA8: FF25B4004D00   jmp    FindNextFileA               ; jmp   d,[0004D00B4]
...
0048D638: FF252C054D00   jmp    DragQueryFileA              ; jmp   d,[0004D052C]
0048D63E: FF2530054D00   jmp    DragFinish                  ; jmp   d,[0004D0530]
0048D644: FF2534054D00   jmp    DragAcceptFiles             ; jmp   d,[0004D0534]
0048D64A: 0000           add    [eax],al
0048D64C: 0000           add    [eax],al
0048D64E: 0000           add    [eax],al
...

  定位到输入表(它的.idata区段同样指向这里):
004D0000:  00 00 00 00.00 00 00 00.00 00 00 00.3C 05 0D 00      ; RVA: 000D053C,VA: 004D053C指向"KERNEL32.dll"
004D0010:  A0 00 0D 00                                          ; FirstThunk的RVA=000D00A0,VA=004D00A0

004D0010:              00 00 00 00.00 00 00 00.00 00 00 00      ; USER32.dll的IMAGE_IMPORT_DESCRIPTOR
004D0020:  49 05 0D 00.F0 01 0D 00                              ; FirstThunk的RVA=000D01F0,VA=004D01F0

004D0020:                          00 00 00 00.00 00 00 00      ; GDI32.dll
004D0030:  00 00 00 00.54 05 0D 00.EC 03 0D 00

004D0030:                                      00 00 00 00      ; ADVAPI32.dll
004D0040:  00 00 00 00.00 00 00 00.5E 05 0D 00.F4 04 0D 00

004D0050:  00 00 00 00.00 00 00 00.00 00 00 00.6B 05 0D 00      ; comdlg32.dll
004D0060:  0C 05 0D 00

004D0060:              00 00 00 00.00 00 00 00.00 00 00 00      ; WINMM.dll
004D0070:  78 05 0D 00.24 05 0D 00

004D0070:                          00 00 00 00.00 00 00 00      ; SHELL32.dll
004D0080:  00 00 00 00.82 05 0D 00.2C 05 0D 00

004D0080:                                      00 00 00 00      ; IMAGE_IMPORT_DESCRIPTOR的所有字段为NULL
004D0090:  00 00 00 00.00 00 00 00.00 00 00 00.00 00 00 00

004D00A0:  8E 05 0D 00.A0 05 0D 00.B0 05 0D 00.BC 05 0D 00      ; KERNEL32.dll的FirstThunk指向这里,IMAGE_THUNK_DATA表
004D00B0:  D8 05 0D 00.E8 05 0D 00
...
004D01E0:  DE 0A 0D 00.F6 0A 0D 00.00 0B 0D 00.00 00 00 00      ; [004D01EC]=0,表示KERNEL32.dll的IMAGE_THUNK_DATA表结束
004D01F0:  0E 0B 0D 00.22 0B 0D 00.32 0B 0D 00.42 0B 0D 00      ; 004D01F0: USER32.dll的FirstThunk指向这里
...
004D0530:  44 18 0D 00.52 18 0D 00.00 00 00 00.4B 45 52 4E  D   R       KERN
004D0540:  45 4C 33 32.2E 64 6C 6C.00 55 53 45.52 33 32 2E  EL32.dll USER32.
004D0550:  64 6C 6C 00.47 44 49 33.32 2E 64 6C.6C 00 41 44  dll GDI32.dll AD
004D0560:  56 41 50 49.33 32 2E 64.6C 6C 00 63.6F 6D 64 6C  VAPI32.dll comdl
004D0570:  67 33 32 2E.64 6C 6C 00.57 49 4E 4D.4D 2E 64 6C  g32.dll WINMM.dl
004D0580:  6C 00 53 48.45 4C 4C 33.32 2E 64 6C.6C 00 00 00  l SHELL32.dll
004D0590:  53 65 74 46.69 6C 65 50.6F 69 6E 74.65 72 00 00  SetFilePointer
004D05A0:  00 00 56 69.72 74 75 61.6C 51 75 65.72 79 00 00    VirtualQuery
004D05B0:  00 00 57 72.69 74 65 46.69 6C 65 00.00 00 49 6E    WriteFile   In
004D05C0:  69 74 69 61.6C 69 7A 65.43 72 69 74.69 63 61 6C  itializeCritical
004D05D0:  53 65 63 74.69 6F 6E 00.00 00 43 72.65 61 74 65  Section   Create
004D05E0:  4D 75 74 65.78 41 00 00.00 00 46 69.6E 64 4E 65  MutexA    FindNe
004D05F0:  78 74 46 69.6C 65 41 00.00 00 46 72.65 65 52 65  xtFileA   FreeRe
...
004D1850:  00 00 00 00.44 72 61 67.41 63 63 65.70 74 46 69      DragAcceptFi
004D1860:  6C 65 73 00.00 00 00 00.00 00 00 00.00 00 00 00  les

  这里有8个IMAGE_IMPORT_DESCRIPTOR,对应7个DLL及一个NULL结构表示列表的结束。Borland的连接器没有生成INT,未联编,只有Name字段和FirstThunk字段有值。第一个IMAGE_IMPORT_DESCRIPTOR的Name指向004D053C,该处为字符串"KERNEL32.dll";FirstThunk指向004D00A0。004D00A0是IMAGE_THUNK_DATA表的开始,第一项的值为RVA 000D058E。VA 004D058E指向IMAGE_IMPORT_BY_NAME结构。
  这个结构的Hint字段为0,Name字段为字符串"SetFilePointer"。联系到上面0048CF8A处:
0048CF8A: FF25A0004D00   jmp    SetFilePointer              ; jmp   d,[0004D00A0]

  注意这是Thunk之前,记住DWORD [0004D00A0]的值为000D058E。我们用OD载入etu_v235.exe,再来看它的值:
...
0048CF8A   $-FF25 A0004D00     JMP [DWORD DS:<&KERNEL32.SetFilePointer>]     ;  kernel32.SetFilePointer
[DS:004D00A0]=7C810C2E (kernel32.SetFilePointer)
...

  Thunk后,DWORD [0004D00A0]的值由000D058E变为7C810C2E,即loader OD将其改写为KERNEL32.dll中函数SetFilePointer的实际入口7C810C2E。

  下面我们来添加一个KERNEL32.dll的WideCharToMultiByte函数。一个办法是在KERNEL32.dll的IMAGE_THUNK_DATA表末尾再插入一项,因为KERNEL32.dll的IMAGE_IMPORT_DESCRIPTOR结构在输入表的头部,这会导致所有表项的指针及前面那一长串JMP的地址需要全部改写,工作量太大。
  为了使修改量最小,我们采取为KERNEL32.dll增加一个IMAGE_IMPORT_DESCRIPTOR结构的办法。
  把上面004D008C~004D009F的空IMAGE_IMPORT_DESCRIPTOR变为第二个KERNEL32.dll的IMAGE_IMPORT_DESCRIPTOR,004D00A0~004D00B3共5个IMAGE_THUNK_DATA项变为空IMAGE_IMPORT_DESCRIPTOR。这样会在第一个KERNEL32.dll的IMAGE_IMPORT_DESCRIPTOR中损失开始的5个函数:SetFilePointer、VirtualQuery、WriteFile、InitializeCriticalSection和CreateMutexA,需要在第二个KERNEL32.dll的IMAGE_IMPORT_DESCRIPTOR补回来。即第一个IMAGE_IMPORT_DESCRIPTOR改为:
004D0000:  00 00 00 00.00 00 00 00.00 00 00 00.3C 05 0D 00      ; RVA: 000D053C,VA: 004D053C指向"KERNEL32.dll"
004D0010:  B4 00 0D 00                                          ; FirstThunk的RVA=000D00B4,[VA=004D00B4]=000D05E8->FindNextFileA

  其中,第一个函数由SetFilePointer变为FindNextFileA,第二个KERNEL32.dll的IMAGE_IMPORT_DESCRIPTOR:
004D0080:                                      00 00 00 00
004D0090:  00 00 00 00.00 00 00 00.3C 05 0D 00.7C 18 0D 00      ; 000D053C还是指向"KERNEL32.dll",FirstThunk=000D187C指向函数SetFilePointer
004D00A0:  00 00 00 00.00 00 00 00.00 00 00 00.00 00 00 00      ; 004D00A0: 空IMAGE_IMPORT_DESCRIPTOR
004D00B0:  00 00 00 00
...

  在整个输入表的尾部004D1864处增加:
004D1860:              00 00 57 69.64 65 43 68.61 72 54 6F        WideCharTo
004D1870:  4D 75 6C 74.69 42 79 74.65 00 00 00.8E 05 0D 00  MultiByte
004D1880:  A0 05 0D 00.B0 05 0D 00.BC 05 0D 00.D8 05 0D 00
004D1890:  64 18 0D 00.00 00 00 00

  这里004D1864~004D1879为新增函数WideCharToMultiByte的IMAGE_IMPORT_BY_NAME,004D187C~004D188F为5个需要补回的函数,004D1890为新增的Thunk。末尾004D1894置0表示此IMAGE_THUNK_DATA表结束。
  还要记住修改一下前面0048CF8A处前5个函数的[XXXXXXXX]:
0048CF8A: FF257C184D00   jmp    SetFilePointer              ; jmp   d,[0004D187C]
0048CF90: FF2580184D00   jmp    VirtualQuery                ; jmp   d,[0004D1880]
0048CF96: FF2584184D00   jmp    WriteFile                   ; jmp   d,[0004D1884]
0048CF9C: FF2588184D00   jmp    InitializeCriticalSection   ; jmp   d,[0004D1888]
0048CFA2: FF258C184D00   jmp    CreateMutexA                ; jmp   d,[0004D188C]

  在这些个JMPs的末尾添加对WideCharToMultiByte函数的调用:
0048D64A: FF2590184D00   jmp    WideCharToMultiByte         ; jmp   d,[0004D1890]

  这样我们在需要用到WideCharToMultiByte函数时,就写一句CALL 0048D64A。
  最后需要修改一下IMAGE_OPTIONAL_HEADER里DataDirectory中输入表的大小:文件偏移00000184处64 18 00 00改为94 18 00 00。

  至此WideCharToMultiByte函数的添加工作完成。接下来开始进行错误修正(Bugfix)。

  通过分析发现菜单命令"Resources | Show menus"下对菜单资源的显示处理部分在调用004319A3里,其中有两处需要修改。第一处为处理菜单的POPUP项,在:
...
00431C1D  |> 8B45 E4               |/MOV EAX,[DWORD SS:EBP-1C]          ; [DWORD SS:EBP-1C]: current input ptr
00431C20  |. 31DB                  ||XOR EBX,EBX
00431C22  |. 66:8B10               ||MOV DX,[WORD DS:EAX]               ; get one char in unicode each time
00431C25  |. 83C0 02               ||ADD EAX,2
00431C28  |. 66:8BDA               ||MOV BX,DX
00431C2B  |. 8945 E4               ||MOV [DWORD SS:EBP-1C],EAX          ; update the current input ptr
00431C2E  |. 66:8995 F4FEFFFF      ||MOV [WORD SS:EBP-10C],DX           ; [WORD SS:EBP-10C]: unicode of current char
00431C35  |. 85DB                  ||TEST EBX,EBX                       ; is unicode null?
00431C37  |. 74 22                 ||JE SHORT etu_v235.00431C5B
00431C39  |. 8D85 28FFFFFF         ||LEA EAX,[DWORD SS:EBP-D8]          ; EAX: output buffer base
00431C3F  |. 8A95 F4FEFFFF         ||MOV DL,[BYTE SS:EBP-10C]           ; here: only the low byte of the unicode used
00431C45  |. 881407                ||MOV [BYTE DS:EDI+EAX],DL           ; EDI: index point to the output buffer
00431C48  |. 8B85 24FFFFFF         ||MOV EAX,[DWORD SS:EBP-DC]          ; [DWORD SS:EBP-DC]:top of input buffer
00431C4E  |. 8B55 E4               ||MOV EDX,[DWORD SS:EBP-1C]
00431C51  |. 47                    ||INC EDI                            ; increase index by 1 byte
00431C52  |. 39C2                  ||CMP EDX,EAX
00431C54  |. 73 05                 ||JNB SHORT etu_v235.00431C5B        ; all input processed?
00431C56  |. 83FF 4B               ||CMP EDI,4B                         ; 4B: output buffer size
00431C59  |.^7C C2                 |\JL SHORT etu_v235.00431C1D
...

  第二处为处理菜单的MENUITEM项,在:
...
00431CDC  |> 8B45 E4               |/MOV EAX,[DWORD SS:EBP-1C]
00431CDF  |. 31DB                  ||XOR EBX,EBX
00431CE1  |. 66:8B10               ||MOV DX,[WORD DS:EAX]
00431CE4  |. 83C0 02               ||ADD EAX,2
00431CE7  |. 66:8BDA               ||MOV BX,DX
00431CEA  |. 8945 E4               ||MOV [DWORD SS:EBP-1C],EAX
00431CED  |. 66:8995 E8FEFFFF      ||MOV [WORD SS:EBP-118],DX           ; [WORD SS:EBP-118]: unicode of current char
00431CF4  |. 85DB                  ||TEST EBX,EBX
00431CF6  |. 74 22                 ||JE SHORT etu_v235.00431D1A
00431CF8  |. 8D85 28FFFFFF         ||LEA EAX,[DWORD SS:EBP-D8]
00431CFE  |. 8A95 E8FEFFFF         ||MOV DL,[BYTE SS:EBP-118]
00431D04  |. 881407                ||MOV [BYTE DS:EDI+EAX],DL
00431D07  |. 8B85 24FFFFFF         ||MOV EAX,[DWORD SS:EBP-DC]
00431D0D  |. 8B55 E4               ||MOV EDX,[DWORD SS:EBP-1C]
00431D10  |. 47                    ||INC EDI
00431D11  |. 39C2                  ||CMP EDX,EAX
00431D13  |. 73 05                 ||JNB SHORT etu_v235.00431D1A
00431D15  |. 83FF 4B               ||CMP EDI,4B
00431D18  |.^7C C2                 |\JL SHORT etu_v235.00431CDC
...

  可以发现,它抛弃了每个unicode字符的高字节。两段结构完全一样,唯一的区别是取出的unicode字符分别临时保存在[WORD SS:EBP-10C]和[WORD SS:EBP-118]。我们在代码区段的末尾0048D650处增加少许代码来转换unicode字符:
0048D64A   $-FF25 90184D00         JMP [DWORD DS:<&KERNEL32.WideCharToMultiByte>]   ; 这里是上面新增的函数
0048D650  /$ 6A 00                 PUSH 0                                           ; /pDefaultCharUsed = NULL
0048D652  |. 6A 00                 PUSH 0                                           ; |pDefaultChar = NULL
0048D654  |. 6A 02                 PUSH 2                                           ; |MultiByteCount = 2
0048D656  |. 8D0438                LEA EAX,[DWORD DS:EAX+EDI]                       ; |
0048D659  |. 50                    PUSH EAX                                         ; |MultiByteStr
0048D65A  |. 6A 01                 PUSH 1                                           ; |WideCharCount = 1
0048D65C  |. 52                    PUSH EDX                                         ; |WideCharStr
0048D65D  |. 6A 00                 PUSH 0                                           ; |Options = 0
0048D65F  |. 6A 00                 PUSH 0                                           ; |CodePage = CP_ACP
0048D661  |. E8 E4FFFFFF           CALL <JMP.&KERNEL32.WideCharToMultiByte>         ; \WideCharToMultiByte, 0048D64A
0048D666  |. 83F8 02               CMP EAX,2                                        ; EAX: 转换后的字符长度
0048D669  |. 75 01                 JNZ SHORT etu_v235.0048D66C                      ; SingleByteStr
0048D66B  |. 47                    INC EDI                                          ; 如果转换的是MultiByte,再加1
0048D66C  |> 8B85 24FFFFFF         MOV EAX,[DWORD SS:EBP-DC]
0048D672  \. C3                    RETN

  函数WideCharToMultiByte的调用参数,具体说明请查MSDN:
int WideCharToMultiByte(
  UINT CodePage,
  DWORD dwFlags,
  LPCWSTR lpWideCharStr,
  int cchWideChar,
  LPSTR lpMultiByteStr,
  int cbMultiByte,
  LPCSTR lpDefaultChar,
  LPBOOL lpUsedDefaultChar
);

  把上面第一处的00431C3F~00431C4D改为:
00431C3F  |. 8D95 F4FEFFFF         ||LEA EDX,[DWORD SS:EBP-10C]
00431C45  |. E8 06BA0500           ||CALL etu_v235.0048D650
00431C4A  |. 90                    ||NOP
00431C4B  |. 90                    ||NOP
00431C4C  |. 90                    ||NOP
00431C4D  |. 90                    ||NOP

  把上面第二处的00431CFE~00431D0C改为:
00431CFE  |. 8D95 E8FEFFFF         ||LEA EDX,[DWORD SS:EBP-118]
00431D04  |. E8 47B90500           ||CALL etu_v235.0048D650
00431D09  |. 90                    ||NOP
00431D0A  |. 90                    ||NOP
00431D0B  |. 90                    ||NOP
00431D0C  |. 90                    ||NOP


  "Show menus"命令就修改完毕。下面解决菜单命令"Resources | Show strings"的问题。字符串资源的显示处理部分在调用00432475里:
00432475  /$ 55                    PUSH EBP
...
004327D3  |. 31C0                  |||XOR EAX,EAX
004327D5  |. 8985 ACFEFFFF         |||MOV [DWORD SS:EBP-154],EAX        ; [DWORD SS:EBP-154]: output index
004327DB  |. EB 1D                 |||JMP SHORT etu_v235.004327FA
004327DD  |> 8B85 ACFEFFFF         |||/MOV EAX,[DWORD SS:EBP-154]
004327E3  |. 8D95 C3FEFFFF         ||||LEA EDX,[DWORD SS:EBP-13D]       ; [DWORD SS:EBP-13D]: output buffer base
004327E9  |. 8BC8                  ||||MOV ECX,EAX
004327EB  |. 01C9                  ||||ADD ECX,ECX                      ; ECX: input index
004327ED  |. 8A0C0E                ||||MOV CL,[BYTE DS:ESI+ECX]         ; ESI: input source base
004327F0  |. 880C10                ||||MOV [BYTE DS:EAX+EDX],CL         ; here again: only the low byte of the unicode used
004327F3  |. 40                    ||||INC EAX
004327F4  |. 8985 ACFEFFFF         ||||MOV [DWORD SS:EBP-154],EAX       ; update output index
004327FA  |> 31C0                  ||| XOR EAX,EAX
004327FC  |. 8B95 ACFEFFFF         ||||MOV EDX,[DWORD SS:EBP-154]
00432802  |. 66:8B45 B0            ||||MOV AX,[WORD SS:EBP-50]          ; input source unicode string length
00432806  |. 39D0                  ||||CMP EAX,EDX
00432808  |. 7E 0D                 ||||JLE SHORT etu_v235.00432817
0043280A  |. 8B85 ACFEFFFF         ||||MOV EAX,[DWORD SS:EBP-154]
00432810  |. 3D EA000000           ||||CMP EAX,0EA                      ; 0EA: output buffer size
00432815  |.^7C C6                 |||\JL SHORT etu_v235.004327DD
00432817  |> 8D85 C3FEFFFF         |||LEA EAX,[DWORD SS:EBP-13D]
0043281D  |. 8B8D ACFEFFFF         |||MOV ECX,[DWORD SS:EBP-154]
00432823  |. 8D9D B0FEFFFF         |||LEA EBX,[DWORD SS:EBP-150]
00432829  |. C60401 00             |||MOV [BYTE DS:ECX+EAX],0           ; set ANSI string ending NULL
...

  同样,它抛弃了每个unicode字符的高字节。改写004327DB~00432801之间的代码为:
004327DB   . 6A 00                 PUSH 0                                     ; /pDefaultCharUsed = NULL
004327DD   > 6A 00                 PUSH 0                                     ; |pDefaultChar = NULL
004327DF   . 68 EA000000           PUSH 0EA                                   ; |MultiByteCount = EA (234.)
004327E4   . 8D85 C3FEFFFF         LEA EAX,[DWORD SS:EBP-13D]                 ; |
004327EA   . 50                    PUSH EAX                                   ; |MultiByteStr
004327EB   . 0FB745 B0             MOVZX EAX,[WORD SS:EBP-50]                 ; |
004327EF   . 50                    PUSH EAX                                   ; |WideCharCount
004327F0   . 56                    PUSH ESI                                   ; |WideCharStr
004327F1   . 6A 00                 PUSH 0                                     ; |Options = 0
004327F3   . 6A 00                 PUSH 0                                     ; |CodePage = CP_ACP
004327F5   . E8 50AE0500           CALL <JMP.&KERNEL32.WideCharToMultiByte>   ; \WideCharToMultiByte
004327FA   . 8985 ACFEFFFF         MOV [DWORD SS:EBP-154],EAX
00432800   . EB 15                 JMP SHORT etu_v235.00432817


  至此,英文版的问题得到解决,我们再看看汉化版的情况。
  chixiaojie的附件中"ETU-DASM 汉化版.exe"被汉化者加了壳,"Exeinfo PE"显示是"MoleBox Pro 2.3640 prod.ver 2.7.0"(跟踪的结果表明为"2.7.0.3570")。代码、数据和资源等区段都被加密压缩,得解开才好修改。
  文件末尾有0xD200字节的Overlay,试了Resty、Sh4DoVV等的Script和Extractor,无解。只好老老实实按CCDebuger和wynney等的帖子在OD里手工解决。OD配置:OllyDbg v1.10英文原版+StrongOD 0.4.6.816,忽略所有异常。
  OD载入目标后停在入口:
005133D3 > $ E8 00000000           CALL ETU-DASM.005133D8
005133D8   $ 60                    PUSHAD
005133D9   . E8 4F000000           CALL ETU-DASM.0051342D
...

  PUSHAD提示在005133D9处HR ESP会很快将我们带到OEP。果然,经F7,F7,HR ESP,F9后:
00512B0E  |. 58                    POP EAX
00512B0F  |. 58                    POP EAX
00512B10  |. FFD0                  CALL EAX     ; ETU-DASM.00410000
...

  OEP处:
00410000   A1 8D134B00             MOV EAX,DWORD PTR DS:[4B138D]
00410005   C1E0 02                 SHL EAX,2
00410008   A3 91134B00             MOV DWORD PTR DS:[4B1391],EAX
0041000D   57                      PUSH EDI
0041000E   51                      PUSH ECX
0041000F   33C0                    XOR EAX,EAX
00410011   BF 00004B00             MOV EDI,ETU-DASM.004B0000
00410016   B9 34134B00             MOV ECX,ETU-DASM.004B1334   ; ASCII "Borland C++ - Copyright 1993 Borland Intl."
...

  结果与前面分析的英文原版一致。通过前面的分析,我们清楚地知道输入表在004D0000:
004D0000  00 00 00 00 FE FF FF FF 00 00 00 00 3C 05 0D 00
004D0010  A0 00 0D 00 00 00 00 00 FE FF FF FF 00 00 00 00
004D0020  49 05 0D 00 F0 01 0D 00 00 00 00 00 FE FF FF FF
004D0030  00 00 00 00 54 05 0D 00 EC 03 0D 00 00 00 00 00
004D0040  FE FF FF FF 00 00 00 00 5E 05 0D 00 F4 04 0D 00
004D0050  00 00 00 00 FE FF FF FF 00 00 00 00 6B 05 0D 00
004D0060  0C 05 0D 00 00 00 00 00 FE FF FF FF 00 00 00 00
004D0070  78 05 0D 00 24 05 0D 00 00 00 00 00 FE FF FF FF
004D0080  00 00 00 00 82 05 0D 00 2C 05 0D 00 00 00 00 00
004D0090  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
004D00A0  00523F90  ETU-DASM.00523F90
004D00A4  7C80B9D1  kernel32.VirtualQuery
004D00A8  7C810D87  kernel32.WriteFile
004D00AC  7C809EF1  kernel32.InitializeCriticalSection
004D00B0  7C80E93F  kernel32.CreateMutexA
004D00B4  00522FD0  ETU-DASM.00522FD0
004D00B8  7C8260C2  kernel32.FreeResource
004D00BC  7C8111DA  kernel32.GetVersion
004D00C0  00523AC0  ETU-DASM.00523AC0
004D00C4  00522FA0  ETU-DASM.00522FA0
004D00C8  7C832EB1  kernel32.LocalUnlock
...
004D01E4  7C86136D  kernel32.WinExec
004D01E8  00523BE0  ETU-DASM.00523BE0
004D01EC  00000000
004D01F0  77D18F9D  user32.GetSystemMetrics
...

  这里我们可以发现,到达OEP时"MoleBox Pro"已经完成了Thunk过程,大部分IMAGE_THUNK_DATA项已经指向了API的实际入口地址,但是某些API,比如KERNEL32.dll的第一项SetFilePointer(004D00A0处)指向了"MoleBox Pro"的附加区段.adata内的一个调用00523F90,这就是所谓的“IAT加密”。ImpREC表明它是"Invalid"的。当然,我们可以找到:
0051C70C  |. 8906                  ||MOV DWORD PTR DS:[ESI],EAX

  这个指令完成Thunk;下面这条指令进行“IAT加密”:
0051CE4D   . 8906                  MOV DWORD PTR DS:[ESI],EAX

  将0051CE4D处2字节NOP就去掉了“IAT加密”。只剩一个004D00C0处的IMAGE_THUNK_DATA项指向00523AC0:
004D00C0  00523AC0  ETU-DASM.00523AC0

  我们来看看它应该指向哪里。"BP VirtualProtect" Shift+F9八次后,原始输入表被解密,这时还未被Thunk。004D00C0指向000D0616,即函数GetProcAddress。之所以它比较特殊,是因为"MoleBox Pro"自身要用它来进行Thunk。通过BP GetProcAddress,可以得到GetProcAddress的实际入口地址为7C80ADA0,填回去,ImpREC就显示完全正常了。
  基本上,"BP VirtualProtect" Shift+F9七次后,OEP所在的代码区段CODE和数据区段TLSCBA得到解密。
  到OEP后,就可以Dump了。奇怪的是,"OllyDump 3.00.110"还Dump不了,不知是什么ANTI。显示"Unable to read memory of debugged process (00400000..0052FFFF)."和"Bad DOS Signature!!"。下面这句脚本也显示相同的提示:
dpe "C:\Temp\ETU-Dasm v2.35 ALPHA\test.exe", 00410000


  LordPE的缺省engine - "LordPE's job"也不行,提示"Couldn't grab process memory : (";用"IntelliDump",提示"0x4B000 of 0x130000 bytes could not be dumped and were padded with zeros.",且Dump的大小约1.18MB。
  Dump的结果修复起来很麻烦。这里就偷懒了,反正只需要“汉化”版的数据区段和资源区段,用下面两句脚本进行Dump:
dm 4B0000, 00015800, "C:\Temp\ETU-Dasm v2.35 ALPHA\4B0000.bin"
dm 500000, 00010000, "C:\Temp\ETU-Dasm v2.35 ALPHA\500000.bin"

  其依据为原始英文版的区段表:
Number  Name   VirtSize   RVA    PhysSize  Offset    Flag
    1 CODE     0007E000 00010000 0007D800 00000600 60000020
    2 TLSCBA   00016000 00090000 00000000 0007DE00 C0000040
    3 TLSCBA   00016000 000B0000 00015800 0007DE00 C0000040
    4 .idata   00002000 000D0000 00001A00 00093600 C0000040
    5 .edata   00001000 000E0000 00000200 00095000 40000040
    6 .reloc   00007000 000F0000 00006200 00095200 50000040
    7 .rsrc    00011000 00100000 00010800 0009B400 50000040

  和"MoleBox Pro"加壳的"ETU-DASM 汉化版.exe"的区段表:
Number  Name   VirtSize   RVA    PhysSize  Offset    Flag
    1 CODE     0007E000 00010000 00034A00 00000400 60000020
    2 TLSCBA   00016000 00090000 00000000 00000000 C0000080
    3 TLSCBA   00016000 000B0000 00006000 00034E00 C0000040
    4 .idata   00002000 000D0000 00000A00 0003AE00 C0000040
    5 .edata   00001000 000E0000 00000200 0003B800 40000040
    6 .reloc   00007000 000F0000 00000000 00000000 40000080
    7 .rsrc    0000FF10 00100000 00010000 0003BA00 40000040
    8 .adata   0001FB20 00110000 00012A00 0004BA00 E0000020

  以及"ETU-DASM 汉化版.exe"在OD中的内存窗口信息:
Address      Size               Owner               Section    Contains    Type            Access   Initial access
004B0000     00016000 (90112.)  ETU-DASM 00400000   TLSCBA                 Imag 01001040   RWE      RWE
00500000     00010000 (65536.)  ETU-DASM 00400000   .rsrc      resources   Imag 01001002   R        RWE

  在改好的英文版的基础上,将区段TLSCBA和区段.rsrc用Dump的结果4B0000.bin和500000.bin分别替换。最后调整一下对应区段和DataDirectory里对应项的信息就大功告成了!
  从Dump的数据区段也可以发现,该汉化版并不“完善”:凡是代码生成的对话框的Caption、主窗口Status栏的文本及错误信息等全部没有“汉化”。当然,汉化者在"汉化说明.txt"中已经说了“汉化重点是右键功能。”;它还有一个“毛病”,汉化者为了美观,所有菜单命令的快捷键将不起作用。已经不错了,将就用吧。

  附件:
  英文版:ETU-Dasm_v2.35_ALPHA.7z
etu_v235.exe
Size: 704,512
MD5:  fd59ecac46ea6ac8e580e06dcef7fde6



  汉化版:ETU-Dasm v2.35 ALPHA CHS.7z
etu_v235.exe
Size: 701,440
MD5:  fc85ce5fbc811eecbf8938fb89113960


ETU-Dasm_v2.35_ALPHA.7z
ETU-Dasm v2.35 ALPHA CHS.7z

【公告】看雪团队招聘安全工程师,将兴趣和工作融合在一起!看雪20年安全圈的口碑,助你快速成长!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (10)
雪    币: 681
活跃值: 活跃值 (47)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
zyqqyz 活跃值 1 2012-8-7 09:30
2
0
占着沙发,果断收藏
雪    币: 7406
活跃值: 活跃值 (395)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hxsoft 活跃值 2012-8-7 09:55
3
0
支持加精!强贴收藏。
雪    币: 2857
活跃值: 活跃值 (202)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yjd 活跃值 2012-8-7 10:06
4
0
楼主v5,又一精品文章。
雪    币: 1408
活跃值: 活跃值 (669)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chixiaojie 活跃值 2012-8-7 10:50
5
0
感谢 MistHill 帮忙,这下终于用上了。
雪    币: 66
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lucaslzx 活跃值 2012-8-14 20:30
6
0
我也蛮喜欢里面的预览功能,最特别的是64位程序流畅打开,可惜这个team好像解散好多年了
雪    币: 248
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dwbclz 活跃值 2012-8-18 14:44
7
0
感谢MistHill分享!!!MistHill 分享的几个修正过的软件都有收藏~
雪    币: 4059
活跃值: 活跃值 (507)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
b23526 活跃值 2012-8-18 15:12
8
0
强文必须收藏
雪    币: 251
活跃值: 活跃值 (31)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
章含羽 活跃值 2012-8-18 22:02
9
0
非常好的反编译器,如果有源码,改用C语言重编译一下就完美了
雪    币: 2857
活跃值: 活跃值 (202)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yjd 活跃值 2012-8-18 22:25
10
0
这篇竟然没评精华?
雪    币: 202
活跃值: 活跃值 (59)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tttlinbing 活跃值 2012-10-25 06:03
11
0
感谢你的分享,谢谢下载下来试试,正需要!
游客
登录 | 注册 方可回帖
返回