• 标 题:Lock98主程序脱壳笔记 (1千字)
  • 作 者:hoto
  • 时 间:2003-1-9 14:18:48
  • 链 接:http://bbs.pediy.com

Lock98是国内第一套基于Windows 32位操作系统的软件加密工具,
自称可以:
        可对抗任何静态分析
        可对抗一切拷贝机
        对抗所有的脱壳工具及拷贝工具

太吹牛了吧,不太相信,就拿它开刀。
--------------------------------------------------------------
说得这么好,也想来一份试一试。于是从他的主页上下载了一份。接下来就开始安装,在安装过程中,任意
输入了一些信息,程序开始复制文件。但很快提示“磁盘密钥信息校验失败”。
来到安装文件夹中,只有几个文件:
DEFAULT  CFG
HDINST32 EXE
LOCK98  INI
LOCK98  HLP
没有主程序文件。

第一步,想办法得到主程序文件。
程序在安装中会提示“磁盘密钥信息校验失败”,很有可能是在校验失败后将主程序文件删除了,
再来一次,启动TRW2000,当安装程序复制文件完成,还没有提示出错时,按 CTRL+N 断下,按几次
F12回到程序领空,下命令 SUSPEND 让进程挂起。

回到安装文件夹中,果然看见了主程序lock98.exe ,马上把它复制一份。再按CTRL+N ,让安装程序继续,等
安装程序完成,lock98.exe 果然被删除了,不过我要的已经有了。运行 lock98.exe ,发现它也是用磁盘加
密的,没有加密盘,程序没法运行。

第二步,处理 lock98.exe 脱去它的磁盘加密的壳

运行lock98.exe后,提示 "加密盘错误",程序无法运行.
用peditor打开lock98.exe,它的 sections 如下:
CODE
DATA
BSS
.idata
.tls
.rdata
.reloc
.rsrc
_LOCK98_ <---看样子这就是通过LOCK98.EXE加密后增加的节,

查看它的引入表,各种引入的DLL及引入API都在,初步判断程序在加密过程中没有对引入表做手脚.

使用TRW2000进行跟踪,在加壳程序中,果然有很多的花指令,动态分析比较麻烦.
为了验证一下它有没有对代码段(即CODE段)进行加密,我找到代码段的一个地址,
对其下断点 BMP .....
运行后程序被断在_LOCK98_中的某处,它在一个循环中不断的对代码段的数据进行还原.可见,程序对原代码段进行的处理,代码段还原后,运行一段时间后开始读加密盘.因为没有加密盘,很快就提示出错.

于时,我就大胆猜想,即然程序没有对引入表做手脚,在读加密盘之前又对代码进行了还原.那么只要在提示出错时,将程序DUMP下来,找到原程序的入口就可以了.
所以马上用 peditor 将程序DUMP下来,保存在磁盘中,接下来就开始想办法找程序入口点.

在DUMP下来的程序中,使用peditor查看其引入表,发现其引入表已经在加载过程中已经被破坏(因为这是一个Borland Delphi 3.0 编译的程序。TLINK32 所产生的 PE 对引入表中的Characteristics [也就是 hint-name array ]总是 0 ,而使 FirstThunk 指向引入函数的名称,而其它编译器会使它指向引入函数的名称。FirstThunk 在程序加载时,会被函数的实际地址填充而造成引入表被破坏),所以接下来就想办法修正引入表。

在原程序中,引入表的DLL和API都很完整,所以首先想到用原程序中的引入表,把它提取出来,放到DUMP下来的程序中。经查在原程序中,引表入在文件中的偏移在5E000处。

          Characteristics                    指向DLL名,在文件中实际为5E728
          __________                          __________
          |        |                          |        |
0005E000  00 00 00 00 00 00 00 00  00 00 00 00 28 C7 06 00  ............(?.
         
  FirstThunk 指向引入函数名的指针数组,在文件中实际指向5e140
          __________
          |        |
0005E010  40 C1 06 00 00 00 00 00  00 00 00 00 00 00 00 00  @?.............
0005E020  80 C9 06 00 D0 C1 06 00  00 00 00 00 00 00 00 00  ?.辛..........
0005E030  00 00 00 00 BA C9 06 00  E0 C1 06 00 00 00 00 00  ....荷..嗔......
0005E040  00 00 00 00 00 00 00 00  FA C9 06 00 F0 C1 06 00  ..........鹆..
...............

          指向引入函数名
          __________
          |        |
0005E140  36 C7 06 00 4E C7 06 00  66 C7 06 00 7E C7 06 00  6?.N?.f?.~?.
0005E150  9A C7 06 00 A8 C7 06 00  B8 C7 06 00 C4 C7 06 00  毲..ㄇ..盖..那..
0005E160  D2 C7 06 00 E2 C7 06 00  F8 C7 06 00 0E C8 06 00  仪..馇.....?.


而在DUMP下来的程序中,引入表在文件中的偏移为6C000处,其它的信息都很完整,但其FirstThunk在加载时被破坏(都是Borland的小错误造成的,让我多花了很多时间):


0006C000  00 00 00 00 21 AF 32 37  00 00 F7 BF 28 C7 06 00  ....!?7..骺(?.
0006C010  40 C1 06 00 00 00 00 00  72 DA 38 37 00 00 F5 BF  @?.....r?7..蹩
0006C020  80 C9 06 00 D0 C1 06 00  00 00 00 00 72 DA 38 37  ?.辛......r?7
0006C030  00 00 E8 BF BA C9 06 00  E0 C1 06 00 00 00 00 00  ..杩荷..嗔......
0006C040  76 26 48 39 00 00 E8 7F  FA C9 06 00 F0 C1 06 00  v&H9..?..鹆..
0006C050  00 00 00 00 21 AF 32 37  00 00 F7 BF 8A CA 06 00  ....!?7..骺娛..
0006C060  10 C2 06 00 00 00 00 00  72 DA 38 37 00 00 E8 BF  .?.....r?7..杩
0006C070  EC CA 06 00 28 C2 06 00  00 00 00 00 21 AF 32 37  焓..(?.....!?7
0006C080  00 00 F7 BF 5C CB 06 00  44 C2 06 00 00 00 00 00  ..骺\?.D?.....
0006C090  72 DA 38 37 00 00 E7 BF  78 CF 06 00 34 C3 06 00  r?7..缈x?.4?.
0006C0A0  00 00 00 00 38 74 5A 35  00 00 F2 BF C6 CF 06 00  ....8tZ5..蚩葡..
0006C0B0  44 C3 06 00 00 00 00 00  72 DA 38 37 00 00 F5 BF  D?.....r?7..蹩
0006C0C0  C0 D4 06 00 6C C4 06 00  00 00 00 00 CF 40 A0 3B  涝..l?.....螥?
0006C0D0  00 00 B7 BF 9E DD 06 00  A0 C6 06 00 00 00 00 00  ..房炤..犉......
0006C0E0  75 DA 38 37 00 00 E1 7F  C8 DF 06 00 04 C7 06 00  u?7..?冗...?.
0006C0F0  00 00 00 00 75 DA 38 37  00 00 CB 7F FE DF 06 00  ....u?7..?..
0006C100  10 C7 06 00 00 00 00 00  21 AF 32 37 00 00 F7 BF  .?.....!?7..骺
0006C110  1A E0 06 00 18 C7 06 00  00 00 00 00 73 DA 38 37  .?..?.....s?7
0006C120  00 00 DE BF 34 E0 06 00  20 C7 06 00 00 00 00 00  ..蘅4?. ?.....
0006C130  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................

  本来就是指向引入函数名的,现在已经变为实际地址。 
            _________
            |      |
0006C140  06 AF F8 BF 82 BA F7 BF  5D BA F7 BF 8F 45 F8 BF  .總瑚縘瑚繌E
0006C150  B6 44 F8 BF 5C 02 F8 BF  50 4A F7 BF 34 49 F7 BF  禗\.PJ骺4I骺
0006C160  C6 0E FA BF 1F 7E F7 BF  01 7E F7 BF 29 74 F7 BF  ?.~骺.~骺)t骺
0006C170  78 73 F7 BF 54 DA 08 C0  15 F3 F9 BF 2D 78 F7 BF  xs骺T??簌?x骺


我就将原程序中的引入表从 5E000 大小为 204E 的引入表,覆盖到DUMP下来的程序的6C000处,再使用peditor查看时,引入表已经显示正常。

找入口点前,先把DUMP下来的程序处理一下,使用 peditor 打开程序,查看程序的sections,看到 _LOCK98_ 这一个节在文件中的偏移在C7000开始,于是,先将节的总数由9改为8,用WINHEX打开,将C7000处开始的无用的数据删除。
找入口点真是件麻烦的事,没有加密盘,要分析它的的运行过程找入口点比较烦,于是就想到了另一种方法。
在很多的程序中,程序的开始处的代码都非常类似:如VC++编译的程序,
第一条语句一般是
PUSH EBP
很快就是一个KERNEL32的函数的调用
GetVersion
这是一个用 Borland Delphi 编译的程序,它也一定有类似的代码。于是找了一个其它的 Delphi 编译的程序,发现它的码代调用的特点是:
第一句 PUSH EBP
而且入口点基本是在代码段的结束的地方。
于是我先将 DUMP下来的程序用peditor 改入口点为 1000 ,再用 w32dasm 反编译,在结束处有以下代码:


:0045DB04 98384500                DWORD 00453898
:0045DB08 98564500                DWORD 00455698
:0045DB0C 60564500                DWORD 00455660
:0045DB10 306C4500                DWORD 00456C30
:0045DB14 006C4500                DWORD 00456C00
:0045DB18 B46F4500                DWORD 00456FB4
:0045DB1C 846F4500                DWORD 00456F84
:0045DB20 5C854500                DWORD 0045855C
:0045DB24 2C854500                DWORD 0045852C


:0045DB28 00000000                BYTE  4 DUP(0)


:0045DB2C 30D9                    xor cl, bl
:0045DB2E 45                      inc ebp
:0045DB2F 00                      BYTE 0


:0045DB30 55                      push ebp 〈---------找到一个 push ebp 的代码
:0045DB31 8BEC                    mov ebp, esp
:0045DB33 83C4F4                  add esp, FFFFFFF4
:0045DB36 B858D94500              mov eax, 0045D958
:0045DB3B E8A87BFAFF              call 004056E8
:0045DB40 A1A4ED4500              mov eax, dword ptr [0045EDA4]
:0045DB45 8B00                    mov eax, dword ptr [eax]
:0045DB47 E8DC0FFDFF              call 0042EB28
:0045DB4C A1A4ED4500              mov eax, dword ptr [0045EDA4]
:0045DB51 8B00                    mov eax, dword ptr [eax]
:0045DB53 83C034                  add eax, 00000034

* Possible StringData Ref from Code Obj ->"Lock98.hlp"
                                  |
:0045DB56 BAB8DB4500              mov edx, 0045DBB8
:0045DB5B E8185EFAFF              call 00403978
:0045DB60 8B0D30ED4500            mov ecx, dword ptr [0045ED30]
:0045DB66 A1A4ED4500              mov eax, dword ptr [0045EDA4]
:0045DB6B 8B00                    mov eax, dword ptr [eax]

* Possible StringData Ref from Code Obj ->"勎@"
                                  |
:0045DB6D 8B15BC6F4500            mov edx, dword ptr [00456FBC]
:0045DB73 E8C80FFDFF              call 0042EB40
:0045DB78 8B0D5CEE4500            mov ecx, dword ptr [0045EE5C]
:0045DB7E A1A4ED4500              mov eax, dword ptr [0045EDA4]
:0045DB83 8B00                    mov eax, dword ptr [eax]

* Possible StringData Ref from Code Obj ->"勎@"
                                  |
:0045DB85 8B1508364500            mov edx, dword ptr [00453608]
:0045DB8B E8B00FFDFF              call 0042EB40
:0045DB90 A15CEE4500              mov eax, dword ptr [0045EE5C]
:0045DB95 8B00                    mov eax, dword ptr [eax]
:0045DB97 E880ECFCFF              call 0042C81C
:0045DB9C A1A4ED4500              mov eax, dword ptr [0045EDA4]
:0045DBA1 8B00                    mov eax, dword ptr [eax]
:0045DBA3 E82410FDFF              call 0042EBCC
:0045DBA8 E8475CFAFF              call 004037F4
:0045DBAD 000000                  BYTE  3 DUP(0)


:0045DBB0 FFFFFFFF                BYTE  4 DUP(0ffh)


:0045DBB4 0A00                    or al, byte ptr [eax]
:0045DBB6 0000                    add byte ptr [eax], al
:0045DBB8 4C                      dec esp
:0045DBB9 6F                      outsd
:0045DBBA 636B39                  arpl dword ptr [ebx+39], ebp
:0045DBBD 382E                    cmp byte ptr [esi], ch
:0045DBBF 686C700000              push 0000706C  〈--------代码结束处


从代码结束处往上找,很快就找到一个 push ebp的指令,指令的地址是:0045DB30
去除基址是:5db30 就先试试它吧。用 peditor 再次打开 DUMP 下来的程序,填写入口点为 5db30
试运行,竟然一次运行成功,哈哈,程序就这样被脱壳了,运气不错。
一个吹大牛的东东就这样搞定了。
-------------------------------------------------------------
从这一次脱壳过程中,我的体会是:
lock98 的加密有很多地方做得不好,
1.在读加密盘以前就将所有的代码段进行的还原。
2.没有对原程序的引入表进行加密
3.没有对内存DUMP进行防范。
还有,找入口点还可以从某种编译程序的特点入手,从它的入口代码的特点去多次尝试,可能会有很大的收获。

  • 标 题:再续一篇:得到完美的程序(真正的PEDIY) (2千字)
  • 作 者:hoto
  • 时 间:2003-1-11 14:43:57
  • 链 接:http://bbs.pediy.com

从上面的过程中,已经得到了脱壳后的文件,运行可以正常运行。但是查看文件的大小,我发现DUMP下来的文件比原文件大了很多,不太满意,再一次开始DIY。

原文件大小 758784 字节,其中加密壳所用的大小是16384(十六进制 4000)字节,这就是说,真正的原文件应该是742400 字节,这才是真正的程序大小。

而DUMP下来的文件大小处理完成时是 815104 字节,比原程序大了很多,造成这结果原因与32位程序的文件结构有关(有空会将此做详细分析),得到这样的脱壳文件当然不是最理想的,于是想把这一个文件再一次进行处理,将其真正做到与原程序一样。

在这一次处理过程中,还是想到了另一个取巧的办法,因为在原程序中,除了代码段被经过变换之外,其它的内容并没有发生变化。于是就想到将DUMP下来的程序中的代码段复制到原程序中,再去除原程序中的加壳的代码。就可以得到一个理想的程序了。

用 peditor 打开原程序,查看它的 sections (节表):

                                             
Sention    Virtual size  Virtual Offset    Raw Size    Raw Offset  Characteristics
CODE        005CBC4        0001000          0005CC00    00000400      E0000020
DATA        ...... 
BSS        ......
.isata
.tls
.rdata
.reloc
.rsrc 

其中:CODE          :是代码段
      Virtual size  :是文件被加载到内存中时的节的大小
      Virtual Offset:是文件被加载到内存中时的节的偏移(加上基地址后就可以得到实际地址)
      Raw Size      :是该节在文件中的实际大小,在这个文件中,代码段的长度是5CC00.
      Raw Offset    :是该节在文件中的实际位置,在这个文件中,代码段位置从400(即1024)处开始

查看 DUMP 下来的程序,它的 sections 如下:

                                             
Sention    Virtual size  Virtual Offset    Raw Size    Raw Offset  Characteristics
CODE        005CBC4        0001000          0005CBC4    00001000      E0000020
DATA        ...... 
BSS        ......
.isata


在这个DUMP文件中,代码段的长度是5CBC4.
在这个DUMP文件中,代码段位置从1000(即4096)处开始.

分析到这里,问题这十分明显了,只要把 DUMP 下来的程序的 1000 开始的 5CBC4 字节的内容(十六进制),复制到原程序中的 400 开始的位置,覆盖原程序的代码段,程序就可以被还原。

再将加壳的16384字节删除,入口点修改一下等后期处理,终于得到一个理想的文件。