Soft-ICE实例起步(Windows版)



Soft-ICE实例起步(Windows版)

● 文 / Jiang Hong


//蒋按:Soft-ice是高级开发必备工具,可惜很多人不知如何使用
// Jiang hong这篇还不错,推荐给大家看看

--------------------------------------------------------------------------------

  为了以后说话方便, 这里把 Soft-ICE 的一些简单使用方法说一下, 以免不通 E 文的同志们找不到中文的 Soft-ICE 说明而抓瞎.

  Soft-ICE 由三部分 (以后说的 Soft-ICE, 如果不加特殊说明, 均指 Soft-ICE for Windows 95 的 2.0 版本以上) 组成: WINICE.EXE, WLDR.EXE (在 3.0 中这个文件叫做 LOADER32.EXE) 和显示驱动程序 SIWVID.386.

  另外, Soft-ICE 在启动的时候要装入一些 DLL/EXE 的函数名信息, 你必须手工指定这些 DLL, 按照:

  exp=d:/path/name.ext

  的格式写在 WINICE.DAT 文件里. 本文附录里面有俺用的 WINICE.DAT, 你可以直接用起来, 省得自己写那么多行了. 注意, 一定要把下面几行包括进去, 否则 WINICE 可能什么东东也拦不到:

  exp=c:/win95/system/kernel32.dll
  exp=c:/win95/system/user32.dll
  exp=c:/win95/system/gdi32.dll
  exp=c:/win95/system/comctl32.dll


  一般我们使用 WLDR (以后把 LOADER32 也称为 WLDR) 来装入一个 EXE 文件或者一个 DLL 文件, 大多数的时候, 我们也可以直接执行 EXE 文件, 通过跟踪它的各种消息来找到它. 启动 WINICE 的热键是 Ctrl+D. 先介绍常规的办法:

  启动 WLDR, 然后选择你要跟的程序, 单击 Load 按钮, 屏幕上一阵乱闪后就进入了文本模式, 这就是 Soft-ICE 的跟踪界面, 虽然简单了点, 但是很友好. 可以像 DOSKEY 那样用光标上下键重复上次输入的内容, 也可以输入上次输入内容的一部分, 然后按光标上键, 上次输入内容就完整地贴了出来.

  一般情况下, 如果装入的一个 NE 程序, WINICE 会直接找到它的入口点, 并且把当前的光标定在 EXE 的头一条指令上; 如果是 PE 程序, WINICE 会停在一个 INVALID 区, 按下 F10 后可以到 EXE 头部.

  比较重要的功能键:

  (1) F10 : 单步执行; CALL, INT 会被跳过;
  (2) F8 : 单步执行; CALL, INT 会被切入;
  (3) F4 : 查看程序画面;
  (4) F11 : 对于 CALL 形式的子程序, 直接执行完毕, 在 RET(F) 之后
       回到 CALL 的下一条指令;


  比较重要的几条命令是:

  (1) G: 执行程序, 后面如果加地址, 则执行到该地址为止, 比如:

    2400:0480    MOV   AH,30
    2400:0482    INT   21
    2400:0484    CMP   AL,09
    2400:0486    JZ   04F9
    2400:0488    MOV   AH,4C
    2400:048A    INT   21


  假设当前 CS:IP (EIP) 为 2400:0480, 如果我们直接执行 G, 就会一 G 到底, 直接回到命令行, 原因在于 DOS 9.00 还没出, 程序直接执行 DOS TERMINATE 功能了.

  如果你把 DOS 版本号先改成 9.0, 然后执行 G 4F9, WINICE 会跑到 2400:04F9 处然后停下; 但是如果你不改版本号也这么来一把, 就也会一 G 到底, 回到命令行了, 因为 2400:04F9 在这种情况下永远不会被执行到.

  (2) P: 单步执行程序; 只执行 P 时, 相当于按下 F10 键; 如果后面加入 P RET 参数, 会执行到最近的一条 RET/RETF 处, 注意, IRET 会被忽略, 所以要小心;

  (3) T: 相当于按下 F8;

  (4) BPINT: 设置中断断点; 格式为 "BPINT 中断号 [条件]"; 在 WINICE 2.0 里面, 条件只能是下面三种之一:

    bpint xx ah=ab
    bpint xx al=cd
    bpint xx ax=abcd


  而在 WINICE 3.0 里面, 条件的格式丰富得多:

    bpint xx if ah==ab
    bpint xx if al==cd
    bpint xx if ax==abcd
    bpint xx if dx->4==abcd (当 DS:DX+4 处的值为 0xabcd 的时候)


  还有一些, 不是很常用, 等到用的时候再说吧.       ;)

  (5) BPX: 设置执行地址断点; 格式为 "BPX 地址", 还以上面的例子来说, 假如我们执行 bpx 486, 然后来一把 G 4F9 的话就不会一 G 到底了, 因为我们在那个判断处设了断点, WINICE 会执行到 2400:0486 然后停下;

  BPX 的第二种用法是我们这个教程的关键, 格式为 "BPX 函数名". 这个函数名可以是任意一个 Windows API 函数, 虚拟机指令, DLL 的引出函数等等. 功能强劲. 比如说, 我们先启动 Notepad, 然后在里面随便敲些东东, 然后按 Ctrl+D, 执行:

  :bpx messageboxa     (不用区分大小写)
  :g


  然后关闭 Notepad, 这时 WINICE 会被激活, 原因是断点条件已经符合了. Notepad 此时将弹出一个消息框提醒你存盘, 这就进入了 MessageBox 函数, 后面加一个 A 是由于我们现在在 Windows 95 里面, 函数是区分字符集的. A 表示 ANSI, W 表示 Wide, 即 Unicode (Wide character-set).

  (6) BPM: 设置内存访问断点; 格式为 "BPM 地址"; 比如: BPM F000:E6F9 会把断点设在内存中存放 BIOS 更新版本号的内存位置上, 只要有程序访问这个地址, WINICE 就会激活. 另外, 还可以单独设定对地址的访问条件: 读(R)/写(W)/执行(X). 只需要在后面把对应的字母加上就可以了. 以那段 "一 G 到底" 为例:

  :bpm 2400:0486 x
  :g


  和 bpx 486 的效果完全一样.

  (7) BMSG: 跟踪 Windows 消息; 格式为 "BMSG 消息名"; 比如我们执行 Notepad, 然后 Ctrl+D 激活 WINICE, 输入:

  :bmsg wm_char
  :g


  然后回到 Notepad 中, 随便按一个键, WINICE 就激活了; 原因在于我们在按键消息上设置了断点.

  (8) BL: 列出所有的断点; 格式为 "BL"; 它会把所有断点按从 0 开始的编号列出;

  (9) BC: 清除断点; 格式为 "BC 断点编号", 这个编号就是 BL 列出的那个; 还有一种格式为 "BC *", 作用是清除所有的断点.

  (10) RFL: 改变标志字; 格式为 "RFL 标志位"; 比如当前 Z 标志位 (零位) 为置位状态, 执行 "rfl z" 之后会被清楚; 如果 C 标志位为清除状态, 那么 "rfl c" 将使之置位; 比如:

  :g 486
  :rfl z
  :g 4f9


  这次就真可以 G 到 4F9 处了.

  (11) A: 进入 WINICE 小汇编状态; 格式为 "A 地址"; 也可以不加地址值, 直接在当前 CS:IP 处汇编; 举例:

  :g 486
  :a 2400:0486
  2400:0486 jnz 4f9
  2400:0488 (按下 ENTER 键结束汇编)
  :g 4f9


  也可以 G 到 4F9 处.

  (12) E: 进入 WINICE 内存修改状态; 格式为 "E 地址"; 也可以不加地址值, 直接在 DS:DX 处修改; 比如:

  :e 2400:0485
  2400:0485 - 09 74 XX XX XX XX XX XX XX XX XX XX XX XX XX XX
  (光标停在 09 下面, 我们输入 09 EB, 然后按 ENTER 结束修改)
  2400:0485 - 09 EB XX XX XX XX XX XX XX XX XX XX XX XX XX XX


  然后 "g 4f9" 就可以到 4F9 啦; 我们输入的 EB 是 JMP 的机器码.

  (13) U: 反汇编; 格式为 "U 地址", 也可以不加地址, 直接在当前 CS:IP (EIP) 往后反汇编 12 行 (行数由 CODE 栏的宽度而定).

  (14) R: 更改寄存器的值; 格式为 "R 寄存器名=值", 也可以不加值, WINICE 会让你在最上面的寄存器区直接修改, 用光标键可以在各个位之间移动. 比如:

  :g 484
  :r ax=0009
  :g 4f9


  就到了 4F9 处.

  基本的指令就是这 14 条, 如果需要, 俺随时补充就是了.




--- 如何拆解 ACDSee '95
  ACDSee '95 是一个速度快, 功能强, 格式多, BUG 少的 ...... 图形浏览程序 (不要想歪了哦! ;) . 我们以 ACDSee '95 1.0 正式版为例来看看此类程序如何拆解.

  首先是得到该程序, 该程序可以在:

  http://www.acdsys.com/download.htm(l)

  下获得. 也可以在国内的许多 BBS 上得到. 注意: 我们需要它的正式版, 否则许多地址值是错误的, 但是原理是一样的.

  首先用用这个程序, 当你看了 30 张左右的图片之后, 该程序就开始频繁提醒你注册了. 另外, 在程序的 About 对话框里面也有 Register 按钮让你注册. 我们来试试注册会是什么样子. 在 Register 对话框里面随便输入一个名字, 比如 "eGIS - pCE '97" 吧, 然后按 Tab 键跑到 Code 栏里面输入个数字, 比如 12345, 然后按回车键.

  啊! ACDSee '95 弹出一只 Message Box, 告诉你输入的号是无效的.   :..(

  让我们来想想看这种判断应该是如何工作的, 这不难猜:

  (1) 取得用户输入的名字;
  (2) 取得用户输入的注册号;
  (3) 使用某种算法检验;
  (4) 判断是否合法;
  (5) 如果合法就显示注册消息;
  (6) 如果非法就弹出一只 Message Box.


  好. 知道了这个就好办. 我们不难看到, 拆解的关键在于上面的第四步, 如何令这个程序认为你输入的号是正确的. 从那个 "一 G 到底" 程序里面我们应该学到些简单的拆解经验, 在那个例子里, 我们就是改动了一个 Z 标志来达到可以 G 到 4F9 的目的的, 这个方法是 "普适" 的, 也适于这个比较复杂的例子.

  现在我们开始拆. 首先我们猜想 ACDSEE 使用了 GetWindowTextA 函数来取得用户输入的信息, 来试试:

  :bpx getwindowtexta
  :g

  然后再按 ENTER 键, 结果 WINICE 没有被激活. 为什么呢? 原因是很简单的啦, 就是 ACDSEE 没用这个函数. 那用的是什么呢? 不难想到 GetDlgItemTextA 函数, 再跟一次看看:

  :bc *
  :bpx getdlgitemtexta
  :g

  按下回车键. Bingo! WINICE 被激活了! 好, 说明我们跟的函数是对的. 但是不要着急, 我们输入了两条信息: 名字和注册号, 因此这个函数会被执行两次, 所以我们继续 G 一下. 果然, WINICE 又拦到一个 GetDlgItemTextA 函数:

  USER32! GetDlgItemTextA
  --------------------------------------------------------------
  0137:BFF61657    MOV   CL,96
  0137:BFF61659    PUSH  EBP
  0137:BFF6165A    MOV   EBP,ESP
  0137:BFF6165C    PUSH  ECX
  0137:BFF6165D    SUB   ESP,3C
  0137:BFF61660    PUSH  WORD PTR [EBP+08]


  好了好了, 就 A 到这儿吧. 反正 WINICE 拦到了这个函数, 并且我们不关心它是如何得到那些数据的, 我们直接走完这个函数. 按下 F11 键就回到了调用它的地方:

  0137:004016FB    CALL  EDI  ; 我们就是从这里回来的    ;)
  0137:004016FD    XOR   DI,DI ; 当前 EIP
  0137:00401700    LEA   EBX,[ESP+18]
  0137:00401704    CMP   BYTE [ESP+18],0
  0137:00401709    JZ   401723
  0137:0040170B    MOVSX  EAX,BYTE PTR [EBX]


  我们看到 EIP 下面两条指令都和 [ESP+18] 有关. 究竟 [ESP+18] 处是什么东东呢? 我们来 DUMP 一下它:

  :d esp+18

  哈哈! 原来 [ESP+18] 处存的是你输入的姓名. 那么下面一条 CMP 的作用很明显了, 它是判断你是不是什么都没有输入, 我们既然输入了姓名, 就可以狠光明正大地 G 到地址 40170B 去也.

  接下来的一段是:

  0137:0040170E   PUSH  EAX
  0137:0040170F   CALL  0045A230
  0137:00401714   ADD   ESP,04
  0137:00401717   TEST  EAX,EAX
  0137:00401719   JZ   0040171D
  0137:0040171B   INC   DI
  0137:0040171D   INC   EBX
  0137:0040171E   CMP   BYTE PTE [EBX],0
  0137:00401721   JNZ   0040170B
  0137:00401723   CMP   DI,05
  0137:00401727   JL   00401841
  0137:0040172D   LEA   EAX,[ESP+38]    ; 注册号
  0137:00401731   LEA   EAX,[ESP+18]    ; 名字
  0137:00401735   PUSH  EAX
  0137:00401736   PUSH  ECX
  0137:00401737   PUSH  0047A128
  0137:0040173C   CALL  00403560
  0137:00401741   ADD   ESP,0C
  0137:00401744   CMP   EAX,01
  0137:00401747   SBB   EAX,EAX
  0137:00401749   INC   EAX
  0137:0040174A   TEST  EAX,EAX
  0137:0040174C   JL   00401841
  0137:00401752   LEA   EAX,[ESP+14]
  0137:00401756   LEA   ECX,[ESP+0C]
  0137:0040175A   PUSH  EAX
  0137:0040175B   PUSH  ECX

  A 了这么长, 我们来看看这段代码的用处吧. 首先我们看到在 40172D 这个地方用到了 [ESP+38] 和 [ESP+18], 经过 DUMP 知道, [ESP+38] 存放着我们输入的序列号, 那么上面的一个循环就可以直接跳过来了. 输入:

  :g 40173c

  然后有一个 CALL, 它前面的几个压栈函数压进去的是名字和序号, 然后经过一个子程序, 然后下面又有判断, 那么这个 CALL 极有可能是判断名字和序号是否符合的子程序, 我们来看看是不是这么回事. 先 G 到下面的判断处:

  :g 401744

  我们看到这时候 EAX 是 0, 然后继续走到 40174C, 发现它是要跳到 401841 执行程序. 我们继续走, 发现那个破框弹了出来. 之前的唯一一个分支就是在 40174C 这个地方了, 因此我们重复上面的步骤跟到 401744, 将 EAX 改成 1, 到 40174C 的时候继续执行下一条指令. 我们来 G 一把.

  Bingo!!! ACDSee '95 告诉我们它已经被注册了. 但是别得意, 看看它的标题栏吧, 还是 [unregistered] 的呢.     :(

  这是怎么回事呢? 不难想到, 还有别的判断. 是不是还要跟呢? 不用啦. 既然我们找到了判断子程序, 就可以直接改它了. 根据我们上次的经验, EAX 是 1 的时候表示名字和注册号是相符的, 那么我们把程序的返回值 EAX 改成恒为 1 就可以了.

  重新跟踪, 到 40173C 这个地方按下 F8 开始. 然后进入程序, 我们用 Ctrl+光标下键移动代码, 到 4035FE 处:

  0137:004035FE    TEST  EAX,EAX
  0137:00403600    MOV   EAX,00000001
  0137:00403605    JZ   00403609
  0137:00403607    XOR   EAX,EAX
  0137:00403609    POP   EDI
  0137:0040360A    POP   ESI
  0137:0040360B    POP   EBX
  0137:0040360C    ADD   ESP,4
  0137:00403612    RET

  如果你有一些反汇编 C++ 编译出的 EXE 的经验, 就可以看到这实际是:

  return ( fValid ? 1 : 0 ) ;

  的编译结果. 可以看到, 403605 是一个关键的地方, 就是这条该死的指令把可爱的 EAX 弄成了 0. 知道这个就好办啦. 我们把它跳过去:

  :a 403605
  0137:00403605 jmp 403609
  0137:00403607 (按回车结束汇编)


  然后重新执行一次. HOHOHO! 这次标题栏也变成注册版的样子啦. 拆解工作完毕!

  回到 DOS 下, 把我们的成果写回 EXE 里面就可以了. 刚才我们改的是把 JZ 改成了 JMP, 也就是说, 把

  B8 01 00 00 00 74 02 33 C0 5F 5E 5B
  改成了:
  B8 01 00 00 00 EB 02 33 C0 5F 5E 5B
  (黑话叫做 "74 改 EB"   :)


  为什么要把要查找的串弄得这么长呢? 直接把 "74 02" 替换成 "EB 02" 不就得了吗? 不是这样的. 在 EXE 里面可能有许多个 "74 02", 因为 JMP $+2 是一条太普通不过的指令了, 而一个 EXE 里面有多个

    MOV   EAX,00000001
    JNZ   @HERE+2
    XOR   EAX,EAX
    POP   EDI
    POP   ESI
    POP   EBX


  的机会就少得多了, 一般情况下只有一处, 这就是我们要改的一处.

  把特征串取得太长了也没什么实际意义, 反而会浪费地球上有限的纸资源, 地球只有一个, 是我们的家, 我们要爱护它 ......

  好, 用一个你喜爱的二进制文件编辑器改掉它. 然后重新执行 ACDSee '95.

  酷! 大功告成啦. 赶紧找个 MM 吹嘘一把 ......   ;)

  爽过以后总结一下经验, 下次泡的时候就是老枪了:

  经验一: 你现在知道了注册码的判断步骤;
  经验二: 你知道了程序可以用 GetDlgItemText 和 GetWindowText 取得
      在 Edit Box 中用户输入的数据;
  经验三: 你知道了程序判断注册号是否合法的地方可能不止一处, 但是一
      般都用同一个子程序来完成检验功能;
  经验四: 你知道了取得替换码的一些原则: 即 --- 不要太长也不要太短.




目录: 本手册的功能 读者对象 本手册的组织结构 排风格 如何使用本手册 其它相关文件 (外国人就是罗嗦,为了求全,只好随他了。) 第一章 欢迎使用SoftICE 产品概况 介绍SoftICE 介绍Symbol Loader 如何得到客户服务 联系NuMega公司的技术支持中心 第二章 安装SoftICE 介绍 硬件和软件要求 SoftICE显示选择 安装之前 安装 安装之后 配置BOOT。INI以便在多处理器系统中支持单CPU 配置SoftICEWindows 95下正确载入 通过串行口连接第二台计算机 解决显卡问题 第三章 SoftICE教程 介绍 载入SoftICE 构造GDIDEMO样本程序 装入GDIDEMO样本程序 控制SoftICE屏幕 通过源码跟踪和逐步调试 读本地数据 设定断点和目标断点 设定一个断点 设定STICKY断点 (此处不知STICKY作何解释。待我读到此处时大概会明白) 使用SoftICE命令行信息 使用符号和符号表 设定条件断点 设置BPX断点 编辑断点 设定读写内存断点 第四章 将代码装入SoftICE 调试的概念 准备调试程序 准备调试设备驱动程序和VxDs(虚拟设备驱动程序) 手工装入SoftICE 装入SoftICE for Windows 95 装入SoftICE for Windows NT 构造带有调试信息的程序 使用Symbol Loader转换和载入文件 修改模块设定 修改通用设定 修改编译设定 修改调试设定 指定程序的源文件 删除符号表 在DOS命令行里使用Symbol Loader 使用Symbol Loader的命令行工具 NMSYM命令行的语法 使用NMSYM来转换符号信息 使用NMSYM来装入模块和符号信息 使用NMSYM来装入或导出符号表 使用NMSYM卸载符号信息 使用NMSYM来保存历史记录 关于NMSYM的信息 第五章 SoftICE导航 介绍 呼出SoftICE窗口 启动时禁用SoftICE 使用SoftICE窗口 调整窗口大小 控制窗口 拷贝和粘贴数据 用鼠标输入命令 得到帮助 使用命令窗口 滚动窗口 键入命令 重呼命令 使用运行时宏 将命令窗口的历史缓存保存到文件 相关命令 控制代码窗口 读信息 在代码窗口键入命令 使用本地窗口 控制本地窗口 扩展和分解堆栈 相关命令 使用观察窗口 控制观察窗口 设定观察表达式 读信息 扩展和分解类型表达式 相关命令 使用寄存器窗口 控制寄存器窗口 读信息 编辑寄存器和标志位 相关命令 使用数据窗口 控制数据窗口 读信息 改变内存地址和格式 编辑内存 辅助表达式 相关命令 第六章 使用SoftICE 在跟踪错误时调试多个程序 跟踪错误 Ring-3 32位保护模式(Win32程序) Ring-0 驱动代码(内核模式设备驱动程序) Ring-3 16位保护模式(16位Win程序) 地址内容 使用INT 0x41 .DOT命令 理解从R-3到R-0的转变 第七章 使用断点 第八章 ----------------------------------------------------------- (...很累人那!今天就这些了吧.因为我也是读一些翻译一些,所以有些"向前引用 "的名词术语不知道要如何解释其意义.应该读完再翻译比较好...不过那个时候我 又开学了...呜...一点时间也没有了!) 第七章 使用断点 介绍 SoftICE所支持的断点类型 断点选项 执行断点 内存断点 中断断点 I/O断点 窗口消息断点 理解断点的含义 虚断点 设置断点活动 条件断点 条件断点计数功能 在条件表达式中使用本地变量 在条件断点里引用堆栈 参考 多重断点 所用时间 断点统计 在表达式中引用断点 维护断点 使用内嵌断点 第八章 使用表达式 表达式 操作符 操作符优先级 形成表达式 表达式类型 定制类型 取得符号的数值 间接使用符号 第九章 装入系统组件符号 装入由DLLEXE文件导出的符号表 使用未命名的入口点
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值