目 录

OllyDB入门教程

制作者：kusky 2007.7.16

    第一卷 OllyDBG入门系列
  • 第一篇 认识OllyDBG
  • 第二篇 字串参考
  • 第三篇 函数参考
  • 第四篇 内存断点
  • 第五篇 消息断点及RUN跟踪
  • 第七篇 汇编功能
    第二卷 OllyDBG技巧系列
  • 第一篇 OllyDBG实用技巧六则
  • 第二篇 OllyDBG之万能断点篇
  • 第三篇 OllyDBG之中断方法篇
  • 第四篇 OllyDBG小技巧
  • 第五篇 OllyDBG暂停技巧
  • 第六篇 OllyDBG条件断点技巧
    第三卷 OllyDBG改造系列
  • 第一篇 轻松打造你自己的OllyDBG
  • 第二篇 打造自己喜欢的OllyDBG
  • 第三篇 打造对抗CMPXCHG8G的OD
  • 第四篇 藏好自己的OllyDBG
  • 第五篇 OllyDBG复制的BUG分析和修正
  • 第六篇 打造处理OutputDebugString("%s")的OD
    第四卷 OllyDBG插件系列
  • 第一篇 认识ODbgScript
  • 第二篇 ODbgScript的脚本命令（1）
  • 第三篇 ODbgScript的脚本命令（2）
  • 第四篇 ODbgScript的注意事项和使用技巧


第一篇 认识OllyDBG

第一卷 OllyDBG 入门系列 第一篇 认识OllyDBG

作者：CCDebuger

一、OllyDBG 的安装与配置

OllyDBG 1.10 版的发布版本是个 ZIP 压缩包，只要解压到一个目录下，运行 OllyDBG.exe 就可以了。汉化版的发布版本是个 RAR 压缩包，同样只需解压到一个目录下运行 OllyDBG.exe 即可：

OllyDBG 中各个窗口的功能如上图。简单解释一下各个窗口的功能，更详细的内容可以参考 TT 小组翻译的中文帮助：

反汇编窗口：显示被调试程序的反汇编代码，标题栏上的地址、HEX 数据、反汇编、注释可以通过在窗口中右击出现的菜单 界面选项->隐藏标题 或 显示标题 来进行切换是否显示。用鼠标左键点击注释标签可以切换注释显示的方式。

寄存器窗口：显示当前所选线程的 CPU 寄存器内容。同样点击标签 寄存器 (FPU) 可以切换显示寄存器的方式。

信息窗口：显示反汇编窗口中选中的第一个命令的参数及一些跳转目标地址、字串等。

数据窗口：显示内存或文件的内容。右键菜单可用于切换显示方式。

堆栈窗口：显示当前线程的堆栈。

要调整上面各个窗口的大小的话，只需左键按住边框拖动，等调整好了，重新启动一下 OllyDBG 就可以生效了。

启动后我们要把插件及 UDD 的目录配置为绝对路径，点击菜单上的 选项->界面，将会出来一个界面选项的对话框，我们点击其中的目录标签：

因为我这里是把 OllyDBG 解压在 F:/OllyDBG 目录下，所以相应的 UDD 目录及插件目录按图上配置。还有一个常用到的标签就是上图后面那个字体，在这里你可以更改 OllyDBG 中显示的字体。上图中其它的选项可以保留为默认，若有需要也可以自己修改。修改完以后点击确定，弹出一个对话框，说我们更改了插件路径，要重新启动 OllyDBG。在这个对话框上点确定，重新启动一下 OllyDBG，我们再到界面选项中看一下，会发现我们原先设置好的路径都已保存了。有人可能知道插件的作用，但对那个 UDD 目录不清楚。我这简单解释一下：这个 UDD 目录的作用是保存你调试的工作。比如你调试一个软件，设置了断点，添加了注释，一次没做完，这时 OllyDBG 就会把你所做的工作保存到这个 UDD 目录，以便你下次调试时可以继续以前的工作。如果不设置这个 UDD 目录，OllyDBG 默认是在其安装目录下保存这些后缀名为 udd 的文件，时间长了就会显的很乱，所以还是建议专门设置一个目录来保存这些文件。

另外一个重要的选项就是调试选项，可通过菜单 选项->调试设置 来配置：

新手一般不需更改这里的选项，默认已配置好，可以直接使用。建议在对 OllyDBG 已比较熟的情况下再来进行配置。上面那个异常标签中的选项经常会在脱壳中用到，建议在有一定调试基础后学脱壳时再配置这里。

除了直接启动 OllyDBG 来调试外，我们还可以把 OllyDBG 添加到资源管理器右键菜单，这样我们就可以直接在 .exe 及 .dll 文件上点右键选择“用Ollydbg打开”菜单来进行调试。要把 OllyDBG 添加到资源管理器右键菜单，只需点菜单 选项->添加到浏览器，将会出现一个对话框，先点击“添加 Ollydbg 到系统资源管理器菜单”，再点击“完成”按钮即可。要从右键菜单中删除也很简单，还是这个对话框，点击“从系统资源管理器菜单删除 Ollydbg”，再点击“完成”就行了。

OllyDBG 支持插件功能，插件的安装也很简单，只要把下载的插件（一般是个 DLL 文件）复制到 OllyDBG 安装目录下的 PLUGIN 目录中就可以了，OllyDBG 启动时会自动识别。要注意的是 OllyDBG 1.10 对插件的个数有限制，最多不能超过 32 个，否则会出错。建议插件不要添加的太多。

到这里基本配置就完成了，OllyDBG 把所有配置都放在安装目录下的 ollydbg.ini 文件中。

二、基本调试方法

OllyDBG 有三种方式来载入程序进行调试，一种是点击菜单 文件->打开 （快捷键是 F3）来打开一个可执行文件进行调试，另一种是点击菜单 文件->附加 来附加到一个已运行的进程上进行调试。注意这里要附加的程序必须已运行。第三种就是用右键菜单来载入程序（不知这种算不算）。一般情况下我们选第一种方式。比如我们选择一个 test.exe 来调试，通过菜单 文件->打开 来载入这个程序，OllyDBG 中显示的内容将会是这样：

调试中我们经常要用到的快捷键有这些：

F2：设置断点，只要在光标定位的位置（上图中灰色条）按F2键即可，再按一次F2键则会删除断点。（相当于 SoftICE 中的 F9）

F8：单步步过。每按一次这个键执行一条反汇编窗口中的一条指令，遇到 CALL 等子程序不进入其代码。（相当于 SoftICE 中的 F10）

F7：单步步入。功能同单步步过(F8)类似，区别是遇到 CALL 等子程序时会进入其中，进入后首先会停留在子程序的第一条指令上。（相当于 SoftICE 中的 F8）

F4：运行到选定位置。作用就是直接运行到光标所在位置处暂停。（相当于 SoftICE 中的 F7）

F9：运行。按下这个键如果没有设置相应断点的话，被调试的程序将直接开始运行。（相当于 SoftICE 中的 F5）

CTR+F9：执行到返回。此命令在执行到一个 ret (返回指令)指令时暂停，常用于从系统领空返回到我们调试的程序领空。（相当于 SoftICE 中的 F12）

ALT+F9：执行到用户代码。可用于从系统领空快速返回到我们调试的程序领空。（相当于 SoftICE 中的 F11）

上面提到的几个快捷键对于一般的调试基本上已够用了。要开始调试只需设置好断点，找到你感兴趣的代码段再按 F8 或 F7 键来一条条分析指令功能就可以了。就写到这了，改天有空再接着灌。


第二篇 字串参考

第一卷 OllyDBG 入门系列 第二篇 字串参考

作者：CCDebuger

上一篇是使用入门，现在我们开始正式进入破解。今天的目标程序是看雪兄《加密与解密》第一版附带光盘中的 crackmes.cjb.net 镜像打包中的 CFF Crackme #3，采用用户名/序列号保护方式。原版加了个 UPX 的壳。刚开始学破解先不涉及壳的问题，我们主要是熟悉用 OllyDBG 来破解的一般方法。我这里把壳脱掉来分析，附件是脱壳后的文件，直接就可以拿来用。先说一下一般软件破解的流程：拿到一个软件先别接着马上用 OllyDBG 调试，先运行一下，有帮助文档的最好先看一下帮助，熟悉一下软件的使用方法，再看看注册的方式。如果是序列号方式可以先输个假的来试一下，看看有什么反应，也给我们破解留下一些有用的线索。如果没有输入注册码的地方，要考虑一下是不是读取注册表或 Key 文件（一般称 keyfile，就是程序读取一个文件中的内容来判断是否注册），这些可以用其它工具来辅助分析。如果这些都不是，原程序只是一个功能不全的试用版，那要注册为正式版本就要自己来写代码完善了。有点跑题了，呵呵。获得程序的一些基本信息后，还要用查壳的工具来查一下程序是否加了壳，若没壳的话看看程序是什么编译器编的，如 VC、Delphi、VB 等。这样的查壳工具有 PEiD 和 FI。有壳的话我们要尽量脱了壳后再来用 OllyDBG 调试，特殊情况下也可带壳调试。下面进入正题：

我们先来运行一下这个 crackme（用 PEiD 检测显示是 Delphi 编的），界面如图：

这个 crackme 已经把用户名和注册码都输好了，省得我们动手^_^。我们在那个“Register now !”按钮上点击一下，将会跳出一个对话框：

好了，今天我们就从这个错误对话框中显示的“Wrong Serial, try again!”来入手。启动 OllyDBG，选择菜单 文件->打开 载入 CrackMe3.exe 文件，我们会停在这里：

我们在反汇编窗口中右击，出来一个菜单，我们在 查找->所有参考文本字串 上左键点击：

当然如果用上面那个 超级字串参考＋ 插件会更方便。但我们的目标是熟悉 OllyDBG 的一些操作，我就尽量使用 OllyDBG 自带的功能，少用插件。好了，现在出来另一个对话框，我们在这个对话框里右击，选择“查找文本”菜单项，输入“Wrong Serial, try again!”的开头单词“Wrong”（注意这里查找内容要区分大小写）来查找，找到一处：

在我们找到的字串上右击，再在出来的菜单上点击“反汇编窗口中跟随”，我们来到这里：

见上图，为了看看是否还有其他的参考，可以通过选择右键菜单查找参考->立即数，会出来一个对话框：

分别双击上面标出的两个地址，我们会来到对应的位置：

00440F79 |. BA 8C104400 MOV EDX,CrackMe3.0044108C ; ASCII "Wrong Serial,try again!"

00440F7E |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]

00440F83 |. 8B00 MOV EAX,DWORD PTR DS:[EAX]

00440F85 |. E8 DEC0FFFF CALL CrackMe3.0043D068

00440F8A |. EB 18 JMP SHORT CrackMe3.00440FA4

00440F8C |> 6A 00 PUSH 0

00440F8E |. B9 80104400 MOV ECX,CrackMe3.00441080 ; ASCII "Beggar off!"

00440F93 |. BA 8C104400 MOV EDX,CrackMe3.0044108C ; ASCII "Wrong Serial,try again!"

00440F98 |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]

00440F9D |. 8B00 MOV EAX,DWORD PTR DS:[EAX]

00440F9F |. E8 C4C0FFFF CALL CrackMe3.0043D068

我们在反汇编窗口中向上滚动一下再看看：

00440F2C |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]

00440F2F |. BA 14104400 MOV EDX,CrackMe3.00441014 ; ASCII "Registered User"

00440F34 |. E8 F32BFCFF CALL CrackMe3.00403B2C ; 关键，要用F7跟进去

00440F39 |. 75 51 JNZ SHORT CrackMe3.00440F8C ; 这里跳走就完蛋

00440F3B |. 8D55 FC LEA EDX,DWORD PTR SS:[EBP-4]

00440F3E |. 8B83 C8020000 MOV EAX,DWORD PTR DS:[EBX+2C8]

00440F44 |. E8 D7FEFDFF CALL CrackMe3.00420E20

00440F49 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]

00440F4C |. BA 2C104400 MOV EDX,CrackMe3.0044102C ; ASCII "GFX-754-IER-954"

00440F51 |. E8 D62BFCFF CALL CrackMe3.00403B2C ; 关键，要用F7跟进去

00440F56 |. 75 1A JNZ SHORT CrackMe3.00440F72 ; 这里跳走就完蛋

00440F58 |. 6A 00 PUSH 0

00440F5A |. B9 3C104400 MOV ECX,CrackMe3.0044103C ; ASCII "CrackMe cracked successfully"

00440F5F |. BA 5C104400 MOV EDX,CrackMe3.0044105C ; ASCII "Congrats! You cracked this CrackMe!"

00440F64 |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]

00440F69 |. 8B00 MOV EAX,DWORD PTR DS:[EAX]

00440F6B |. E8 F8C0FFFF CALL CrackMe3.0043D068

00440F70 |. EB 32 JMP SHORT CrackMe3.00440FA4

00440F72 |> 6A 00 PUSH 0

00440F74 |. B9 80104400 MOV ECX,CrackMe3.00441080 ; ASCII "Beggar off!"

00440F79 |. BA 8C104400 MOV EDX,CrackMe3.0044108C ; ASCII "Wrong Serial,try again!"

00440F7E |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]

00440F83 |. 8B00 MOV EAX,DWORD PTR DS:[EAX]

00440F85 |. E8 DEC0FFFF CALL CrackMe3.0043D068

00440F8A |. EB 18 JMP SHORT CrackMe3.00440FA4

00440F8C |> 6A 00 PUSH 0

00440F8E |. B9 80104400 MOV ECX,CrackMe3.00441080 ; ASCII "Beggar off!"

00440F93 |. BA 8C104400 MOV EDX,CrackMe3.0044108C ; ASCII "Wrong Serial,try again!"

00440F98 |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]

00440F9D |. 8B00 MOV EAX,DWORD PTR DS:[EAX]

00440F9F |. E8 C4C0FFFF CALL CrackMe3.0043D068

大家注意看一下上面的注释，我在上面标了两个关键点。有人可能要问，你怎么知道那两个地方是关键点？其实很简单，我是根据查看是哪条指令跳到“wrong serial,try again”这条字串对应的指令来决定的。如果你在 调试选项->CPU 标签中把“显示跳转路径”及其下面的两个“如跳转未实现则显示灰色路径”、“显示跳转到选定命令的路径”都选上的话，就会看到是从什么地方跳到出错字串处的：

我们在上图中地址 00440F2C 处按 F2 键设个断点，现在我们按 F9 键，程序已运行起来了。我在上面那个编辑框中随便输入一下，如 CCDebuger，下面那个编辑框我还保留为原来的“754-GFX-IER-954”，我们点一下那个“Register now !”按钮，呵，OllyDBG 跳了出来，暂停在我们下的断点处。我们看一下信息窗口，你应该发现了你刚才输入的内容了吧？我这里显示是这样：

堆栈 SS:[0012F9AC]00D44DB4, (ASCII "CCDebuger")

EAX00000009

上面的内存地址 00D44DB4 中就是我们刚才输入的内容，我这里是 CCDebuger。你可以在 堆栈 SS:[0012F9AC]00D44DB4, (ASCII "CCDebuger") 这条内容上左击选择一下，再点右键，在弹出菜单中选择“数据窗口中跟随数值”，你就会在下面的数据窗口中看到你刚才输入的内容。而 EAX00000009 指的是你输入内容的长度。如我输入的 CCDebuger 是9个字符。如下图所示：

现在我们来按 F8 键一步步分析一下：

00440F2C |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] ; 把我们输入的内容送到EAX，我这里是“CCDebuger”

00440F2F |. BA 14104400 MOV EDX,CrackMe3.00441014 ; ASCII "Registered User"

00440F34 |. E8 F32BFCFF CALL CrackMe3.00403B2C ; 关键，要用F7跟进去

00440F39 |. 75 51 JNZ SHORT CrackMe3.00440F8C ; 这里跳走就完蛋

当我们按 F8 键走到 00440F34 |. E8 F32BFCFF CALL CrackMe3.00403B2C 这一句时，我们按一下 F7 键，进入这个 CALL，进去后光标停在这一句：

我们所看到的那些 PUSH EBX、 PUSH ESI 等都是调用子程序保存堆栈时用的指令，不用管它，按 F8 键一步步过来，我们只关心关键部分：

00403B2C /$ 53 PUSH EBX

00403B2D |. 56 PUSH ESI

00403B2E |. 57 PUSH EDI

00403B2F |. 89C6 MOV ESI,EAX ; 把EAX内我们输入的用户名送到 ESI

00403B31 |. 89D7 MOV EDI,EDX ; 把EDX内的数据“Registered User”送到EDI

00403B33 |. 39D0 CMP EAX,EDX ; 用“Registered User”和我们输入的用户名作比较

00403B35 |. 0F84 8F000000 JE CrackMe3.00403BCA ; 相同则跳

00403B3B |. 85F6 TEST ESI,ESI ; 看看ESI中是否有数据，主要是看看我们有没有输入用户名

00403B3D |. 74 68 JE SHORT CrackMe3.00403BA7 ; 用户名为空则跳

00403B3F |. 85FF TEST EDI,EDI

00403B41 |. 74 6B JE SHORT CrackMe3.00403BAE

00403B43 |. 8B46 FC MOV EAX,DWORD PTR DS:[ESI-4] ; 用户名长度送EAX

00403B46 |. 8B57 FC MOV EDX,DWORD PTR DS:[EDI-4] ; “Registered User”字串的长度送EDX

00403B49 |. 29D0 SUB EAX,EDX ; 把用户名长度和“Registered User”字串长度相减

00403B4B |. 77 02 JA SHORT CrackMe3.00403B4F ; 用户名长度大于“Registered User”长度则跳

00403B4D |. 01C2 ADD EDX,EAX ; 把减后值与“Registered User”长度相加，即用户名长度

00403B4F |> 52 PUSH EDX

00403B50 |. C1EA 02 SHR EDX,2 ; 用户名长度值右移2位，这里相当于长度除以4

00403B53 |. 74 26 JE SHORT CrackMe3.00403B7B ; 上面的指令及这条指令就是判断用户名长度最少不能低于4

00403B55 |> 8B0E MOV ECX,DWORD PTR DS:[ESI] ; 把我们输入的用户名送到ECX

00403B57 |. 8B1F MOV EBX,DWORD PTR DS:[EDI] ; 把“Registered User”送到EBX

00403B59 |. 39D9 CMP ECX,EBX ; 比较

00403B5B |. 75 58 JNZ SHORT CrackMe3.00403BB5 ; 不等则完蛋

根据上面的分析，我们知道用户名必须是“Registered User”。我们按 F9 键让程序运行，出现错误对话框，点确定，重新在第一个编辑框中输入“Registered User”，再次点击那个“Register now !”按钮，被 OllyDBG 拦下。因为地址 00440F34 处的那个 CALL 我们已经分析清楚了，这次就不用再按 F7 键跟进去了，直接按 F8 键通过。我们一路按 F8 键，来到第二个关键代码处：

00440F49 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] ; 取输入的注册码

00440F4C |. BA 2C104400 MOV EDX,CrackMe3.0044102C ; ASCII "GFX-754-IER-954"

00440F51 |. E8 D62BFCFF CALL CrackMe3.00403B2C ; 关键，要用F7跟进去

00440F56 |. 75 1A JNZ SHORT CrackMe3.00440F72 ; 这里跳走就完蛋

大家注意看一下，地址 00440F51 处的 CALL CrackMe3.00403B2C 和上面我们分析的地址 00440F34 处的 CALL CrackMe3.00403B2C 是不是汇编指令都一样啊？这说明检测用户名和注册码是用的同一个子程序。而这个子程序 CALL 我们在上面已经分析过了。我们执行到现在可以很容易得出结论，这个 CALL 也就是把我们输入的注册码与 00440F4C 地址处指令后的“GFX-754-IER-954”作比较，相等则 OK。好了，我们已经得到足够的信息了。现在我们在菜单 查看->断点 上点击一下，打开断点窗口（也可以通过组合键 ALT+B 或点击工具栏上那个“B”图标打开断点窗口）：

为什么要做这一步，而不是把这个断点删除呢？这里主要是为了保险一点，万一分析错误，我们还要接着分析，要是把断点删除了就要做一些重复工作了。还是先禁用一下，如果经过实际验证证明我们的分析是正确的，再删不迟。现在我们把断点禁用，在 OllyDBG 中按 F9 键让程序运行。输入我们经分析得出的内容：

用户名：Registered User

注册码：GFX-754-IER-954

点击“Register now !”按钮，呵呵，终于成功了：


第三篇 函数参考

第一卷 OllyDBG 入门系列 第三篇 函数参考

作者：CCDebuger

现在进入第三篇，这一篇我们重点讲解怎样使用 OllyDBG 中的函数参考（即名称参考）功能。仍然选择 crackmes.cjb.net 镜像打包中的一个名称为 CrackHead 的crackme。老规矩，先运行一下这个程序看看：

呵，竟然没找到输入注册码的地方！别急，我们点一下程序上的那个菜单“Shit”（真是 Shit 啊，呵呵），在下拉菜单中选“Try It”，会来到如下界面：

我们点一下那个“Check It”按钮试一下，哦，竟然没反应！我再输个“78787878”试试，还是没反应。再试试输入字母或其它字符，输不进去。由此判断注册码应该都是数字，只有输入正确的注册码才有动静。用 PEiD 检测一下，结果为 MASM32 / TASM32，怪不得程序比较小。信息收集的差不多了，现在关掉这个程序，我们用 OllyDBG 载入，按 F9 键直接让它运行起来，依次点击上面图中所说的菜单，使被调试程序显示如上面的第二个图。先不要点那个“Check It”按钮，保留上图的状态。现在我们没有什么字串好参考了，我们就在 API 函数上下断点，来让被调试程序中断在我们希望的地方。我们在 OllyDBG 的反汇编窗口中右击鼠标，在弹出菜单中选择 查找->当前模块中的名称 (标签)，或者我们通过按 CTR+N 组合键也可以达到同样的效果（注意在进行此操作时要在 OllyDBG 中保证是在当前被调试程序的领空，我在第一篇中已经介绍了领空的概念，如我这里调试这个程序时 OllyDBG 的标题栏显示的就是“[CPU - 主线程, 模块 - CrackHea]”，这表明我们当前在被调试程序的领空）。通过上面的操作后会弹出一个对话框，如图：

对于这样的编辑框中输注册码的程序我们要设断点首选的 API 函数就是 GetDlgItemText 及 GetWindowText。每个函数都有两个版本，一个是 ASCII 版，在函数后添加一个 A 表示，如 GetDlgItemTextA，另一个是 UNICODE 版，在函数后添加一个 W 表示。如 GetDlgItemTextW。对于编译为 UNCODE 版的程序可能在 Win98 下不能运行，因为 Win98 并非是完全支持 UNICODE 的系统。而 NT 系统则从底层支持 UNICODE，它可以在操作系统内对字串进行转换，同时支持 ASCII 和 UNICODE 版本函数的调用。一般我们打开的程序看到的调用都是 ASCII 类型的函数，以“A”结尾。又跑题了，呵呵。现在回到我们调试的程序上来，我们现在就是要找一下我们调试的程序有没有调用 GetDlgItemTextA 或 GetWindowTextA 函数。还好，找到一个 GetWindowTextA。在这个函数上右击，在弹出菜单上选择“在每个参考上设置断点”，我们会在 OllyDBG 窗口最下面的那个状态栏里看到“已设置 2 个断点”。另一种方法就是那个 GetWindowTextA 函数上右击，在弹出菜单上选择“查找输入函数参考”（或者按回车键），将会出现下面的对话框：

看上图，我们可以把两条都设上断点。这个程序只需在第一条指令设断点就可以了。好，我们现在按前面提到的第一条方法，就是“在每个参考上设置断点”，这样上图中的两条指令都会设上断点。断点设好后我们转到我们调试的程序上来，现在我们在被我们调试的程序上点击那个“Check It”按钮，被 OllyDBG 断下：

00401323 |. E8 4C010000 CALL <JMP.&USER32.GetWindowTextA> ; GetWindowTextA

00401328 |. E8 A5000000 CALL CrackHea.004013D2 ; 关键，要按F7键跟进去

0040132D |. 3BC6 CMP EAX,ESI ; 比较

0040132F |. 75 42 JNZ SHORT CrackHea.00401373 ; 不等则完蛋

00401331 |. EB 2C JMP SHORT CrackHea.0040135F

00401333 |. 4E 6F 77 20 7> ASCII "Now write a keyg"

00401343 |. 65 6E 20 61 6> ASCII "en and tut and y"

00401353 |. 6F 75 27 72 6> ASCII "ou\'re done.",0

0040135F |> 6A 00 PUSH 0 ; Style MB_OK|MB_APPLMODAL

00401361 |. 68 0F304000 PUSH CrackHea.0040300F ; Title "Crudd\'s Crack Head"

00401366 |. 68 33134000 PUSH CrackHea.00401333 ; Text "Now write a keygen and tut and you\'re done."

0040136B |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; hOwner

0040136E |. E8 19010000 CALL <JMP.&USER32.MessageBoxA> ; MessageBoxA

从上面的代码，我们很容易看出 00401328 地址处的 CALL CrackHea.004013D2 是关键，必须仔细跟踪。而注册成功则会显示一个对话框，标题是“Crudd\'s Crack Head”，对话框显示的内容是“Now write a keygen and tut and you\'re done.”现在我按一下 F8，准备步进到 00401328 地址处的那条 CALL CrackHea.004013D2 指令后再按 F7 键跟进去。等等，怎么回事？怎么按一下 F8 键跑到这来了：

00401474 $- FF25 2C204000 JMP DWORD PTR DS:[<&USER32.GetWindowText> ; USER32.GetWindowTextA

0040147A $- FF25 30204000 JMP DWORD PTR DS:[<&USER32.LoadCursorA>] ; USER32.LoadCursorA

00401480 $- FF25 1C204000 JMP DWORD PTR DS:[<&USER32.LoadIconA>] ; USER32.LoadIconA

00401486 $- FF25 20204000 JMP DWORD PTR DS:[<&USER32.LoadMenuA>] ; USER32.LoadMenuA

0040148C $- FF25 24204000 JMP DWORD PTR DS:[<&USER32.MessageBoxA>] ; USER32.MessageBoxA

原来是跳到另一个断点了。这个断点我们不需要，按一下 F2 键删掉它吧。删掉 00401474 地址处的断点后，我再按 F8 键，呵，完了，跑到 User32.dll 的领空了。看一下 OllyDBG 的标题栏：“[CPU - 主线程, 模块 - USER32]，跑到系统领空了，OllyDBG 反汇编窗口中显示代码是这样：

77D3213C 6A 0C PUSH 0C

77D3213E 68 A021D377 PUSH USER32.77D321A0

77D32143 E8 7864FEFF CALL USER32.77D185C0

怎么办？别急，我们按一下 ALT+F9 组合键，呵，回来了：

00401328 |. E8 A5000000 CALL CrackHea.004013D2 ; 关键，要按F7键跟进去

0040132D |. 3BC6 CMP EAX,ESI ; 比较

0040132F |. 75 42 JNZ SHORT CrackHea.00401373 ; 不等则完蛋

光标停在 00401328 地址处的那条指令上。现在我们按 F7 键跟进：

004013D2 /$ 56 PUSH ESI ; ESI入栈

004013D3 |. 33C0 XOR EAX,EAX ; EAX清零

004013D5 |. 8D35 C4334000 LEA ESI,DWORD PTR DS:[4033C4] ; 把注册码框中的数值送到ESI

004013DB |. 33C9 XOR ECX,ECX ; ECX清零

004013DD |. 33D2 XOR EDX,EDX ; EDX清零

004013DF |. 8A06 MOV AL,BYTE PTR DS:[ESI] ; 把注册码中的每个字符送到AL

004013E1 |. 46 INC ESI ; 指针加1，指向下一个字符

004013E2 |. 3C 2D CMP AL,2D ; 把取得的字符与16进制值为2D的字符(即“-”)比较，这里主要用于判断输入的是不是负数

004013E4 |. 75 08 JNZ SHORT CrackHea.004013EE ; 不等则跳

004013E6 |. BA FFFFFFFF MOV EDX,-1 ; 如果输入的是负数，则把-1送到EDX，即16进制FFFFFFFF

004013EB |. 8A06 MOV AL,BYTE PTR DS:[ESI] ; 取“-”号后的第一个字符

004013ED |. 46 INC ESI ; 指针加1，指向再下一个字符

004013EE |> EB 0B JMP SHORT CrackHea.004013FB

004013F0 |> 2C 30 SUB AL,30 ; 每位字符减16进制的30，因为这里都是数字，如1的ASCII码是“31H”，减30H后为1，即我们平时看到的数值

004013F2 |. 8D0C89 LEA ECX,DWORD PTR DS:[ECX+ECX*4] ; 把前面运算后保存在ECX中的结果乘5再送到ECX

004013F5 |. 8D0C48 LEA ECX,DWORD PTR DS:[EAX+ECX*2] ; 每位字符运算后的值与2倍上一位字符运算后值相加后送ECX

004013F8 |. 8A06 MOV AL,BYTE PTR DS:[ESI] ; 取下一个字符

004013FA |. 46 INC ESI ; 指针加1，指向再下一个字符

004013FB |> 0AC0 OR AL,AL

004013FD |.^ 75 F1 JNZ SHORT CrackHea.004013F0 ; 上面一条和这一条指令主要是用来判断是否已把用户输入的注册码计算完

004013FF |. 8D040A LEA EAX,DWORD PTR DS:[EDX+ECX] ; 把EDX中的值与经过上面运算后的ECX中值相加送到EAX

00401402 |. 33C2 XOR EAX,EDX ; 把EAX与EDX异或。如果我们输入的是负数，则此处功能就是把EAX中的值取反

00401404 |. 5E POP ESI ; ESI出栈。看到这条和下一条指令，我们要考虑一下这个ESI的值是哪里运算得出的呢？

00401405 |. 81F6 53757A79 XOR ESI,797A7553 ; 把ESI中的值与797A7553H异或

0040140B /. C3 RETN

这里留下了一个问题：那个 ESI 寄存器中的值是从哪运算出来的？先不管这里，我们接着按 F8 键往下走，来到 0040140B 地址处的那条 RETN 指令（这里可以通过在调试选项的“命令”标签中勾选“使用 RET 代替 RETN”来更改返回指令的显示方式），再按一下 F8，我们就走出 00401328 地址处的那个 CALL 了。现在我们回到了这里：

0040132D |. 3BC6 CMP EAX,ESI ; 比较

0040132F |. 75 42 JNZ SHORT CrackHea.00401373 ; 不等则完蛋

光标停在了 0040132D 地址处的那条指令上。根据前面的分析，我们知道 EAX 中存放的是我们输入的注册码经过计算后的值。我们来看一下信息窗口：

ESIE6B5F2F9

EAXFF439EBE

左键选择信息窗口中的 ESIE6B5F2F9，再按右键，在弹出菜单上选“修改寄存器”，我们会看到这样一个窗口：

可能你的显示跟我不一样，因为这个 crackme 中已经说了每个机器的序列号不一样。关掉上面的窗口，再对信息窗口中的 EAXFF439EBE 做同样操作：

由上图我们知道了原来前面分析的对我们输入的注册码进行处理后的结果就是把字符格式转为数字格式。我们原来输入的是字串“12345666”，现在转换为了数字 12345666。这下就很清楚了，随便在上面那个修改 ESI 图中显示的有符号或无符号编辑框中复制一个，粘贴到我们调试的程序中的编辑框中试一下：

呵呵，成功了。且慢高兴，这个 crackme 是要求写出注册机的。我们先不要求写注册机，但注册的算法我们要搞清楚。还记得我在前面说到的那个 ESI 寄存器值的问题吗？现在看看我们上面的分析，其实对做注册机来说是没有多少帮助的。要搞清注册算法，必须知道上面那个 ESI 寄存器值是如何产生的，这弄清楚后才能真正清楚这个 crackme 算法。今天就先说到这里，关于如何追出 ESI 寄存器的值我就留到下一篇 OllyDBG 入门系列（四）－内存断点 中再讲吧。


第四篇 内存断点

第一卷 OllyDBG 入门系列 第四篇 内存断点

作者：CCDebuger

还记得上一篇《OllyDBG 入门系列（三）－函数参考》中的内容吗？在那篇文章中我们分析后发现一个 ESI 寄存器值不知是从什么地方产生的，要弄清这个问题必须要找到生成这个 ESI 值的计算部分。今天我们的任务就是使用 OllyDBG 的内存断点功能找到这个地方，搞清楚这个值是如何算出来的。这次分析的目标程序还是上一篇的那个 crackme，附件我就不再上传了，用上篇中的附件就可以了。下面我们开始：

还记得我们上篇中所说的关键代码的地方吗？温习一下：

00401323 |. E8 4C010000 CALL <JMP.&USER32.GetWindowTextA> ; GetWindowTextA

00401328 |. E8 A5000000 CALL CrackHea.004013D2 ; 关键，要按F7键跟进去

0040132D |. 3BC6 CMP EAX,ESI ; 比较

0040132F |. 75 42 JNZ SHORT CrackHea.00401373 ; 不等则完蛋

我们重新用 OllyDBG 载入目标程序，F9运行来到上面代码所在的地方（你上次设的断点应该没删吧？），我们向上看看能不能找到那个 ESI 寄存器中最近是在哪里赋的值。哈哈，原来就在附近啊:

我们现在知道 ESI 寄存器的值是从内存地址 40339C 中送过来的，那内存地址 40339C 中的数据是什么时候产生的呢？大家注意，我这里信息窗口中显示的是 DS:[0040339C]9FCF87AA，你那可能是 DS:[0040339C]XXXXXXXX，这里的 XXXXXXXX 表示的是其它的值，就是说与我这里显示的 9FCF87AA 不一样。我们按上图的操作在数据窗口中看一下：

从上图我们可以看出内存地址 40339C 处的值已经有了，说明早就算过了。现在怎么办呢？我们考虑一下，看情况程序是把这个值算出来以后写在这个内存地址，那我们要是能让 OllyDBG 在程序开始往这个内存地址写东西的时候中断下来，不就有可能知道目标程序是怎么算出这个值的吗？说干就干，我们在 OllyDBG 的菜单上点 调试->重新开始，或者按 CTR+F2 组合键（还可以点击工具栏上的那个有两个实心左箭头的图标）来重新载入程序。这时会跳出一个“进程仍处于激活状态”的对话框（我们可以在在调试选项的安全标签下把“终止活动进程时警告”这条前面的勾去掉，这样下次就不会出现这个对话框了），问我们是否要终止进程。这里我们选“是”，程序被重新载入，我们停在下面这一句上：

00401000 >/$ 6A 00 PUSH 0 ; pModule NULL

现在我们就要来设内存断点了。在 OllyDBG 中一般我们用到的内存断点有内存访问和内存写入断点。内存访问断点就是指程序访问内存中我们指定的内存地址时中断，内存写入断点就是指程序往我们指定的内存地址中写东西时中断。更多关于断点的知识大家可以参考 论坛精华7->基础知识->断点技巧->断点原理 这篇 Lenus 兄弟写的《如何对抗硬件断点之一 --- 调试寄存器》文章，也可以看这个帖：http://bbs.pediy.com/showthread.php?threadid10829。根据当前我们调试的具体程序的情况，我们选用内存写入断点。还记得前面我叫大家记住的那个 40339C 内存地址吗？现在我们要用上了。我们先在 OllyDBG 的数据窗口中左键点击一下，再右击，会弹出一个如下图所示的菜单。我们选择其中的转到->表达式（也可以左键点击数据窗口后按 CTR+G 组合键）。如下图：

现在将会出现这样一个对话框：

我们在上面那个编辑框中输入我们想查看内容的内存地址 40339C，然后点确定按钮，数据窗口中显示如下：

我们可以看到，40339C 地址开始处的这段内存里面还没有内容。我们现在在 40339C 地址处后面的 HEX 数据或 ASCII 栏中按住左键往后拖放，选择一段。内存断点的特性就是不管你选几个字节，OllyDBG 都会分配 4096 字节的内存区。这里我就选从 40339C 地址处开始的四个字节，主要是为了让大家提前了解一下硬件断点的设法，因为硬件断点最多只能选 4 个字节。选中部分会显示为灰色。选好以后松开鼠标左键，在我们选中的灰色部分上右击：

经过上面的操作，我们的内存断点就设好了（这里还有个要注意的地方：内存断点只在当前调试的进程中有效，就是说你如果重新载入程序的话内存断点就自动删除了。且内存断点每一时刻只能有一个。就是说你不能像按 F2 键那样同时设置多个断点）。现在按 F9 键让程序运行，呵，OllyDBG 中断了！

7C932F39 8808 MOV BYTE PTR DS:[EAX],CL ; 这就是我们第一次断下来的地方

7C932F3B 40 INC EAX

7C932F3C 4F DEC EDI

7C932F3D 4E DEC ESI

7C932F3E ^ 75 CB JNZ SHORT ntdll.7C932F0B

7C932F40 8B4D 10 MOV ECX,DWORD PTR SS:[EBP+10]

上面就是我们中断后反汇编窗口中的代码。如果你是其它系统，如 Win98 的话，可能会有所不同。没关系，这里不是关键。我们看一下领空，原来是在 ntdll.dll 内。系统领空，我们现在要考虑返回到程序领空。返回前我们看一下数据窗口：

现在我们转到反汇编窗口，右击鼠标，在弹出菜单上选择断点->删除内存断点，这样内存断点就被删除了。

现在我们来按一下 ALT+F9 组合键，我们来到下面的代码：

00401431 |. 8D35 9C334000 LEA ESI,DWORD PTR DS:[40339C] ; ALT+F9返回后来到的位置

00401437 |. 0FB60D EC334000 MOVZX ECX,BYTE PTR DS:[4033EC]

0040143E |. 33FF XOR EDI,EDI

我们把反汇编窗口往上翻翻，呵，原来就在我们上一篇分析的代码下面啊？

现在我们在 0040140C 地址处那条指令上按 F2 设置一个断点，现在我们按 CTR+F2 组合键重新载入程序，载入后按 F9 键运行，我们将会中断在我们刚才在 0040140C 地址下的那个断点处：

0040140C /$ 60 PUSHAD

0040140D |. 6A 00 PUSH 0 ; /RootPathName NULL

0040140F |. E8 B4000000 CALL <JMP.&KERNEL32.GetDriveTypeA> ; /GetDriveTypeA

00401414 |. A2 EC334000 MOV BYTE PTR DS:[4033EC],AL ; 磁盘类型参数送内存地址4033EC

00401419 |. 6A 00 PUSH 0 ; /pFileSystemNameSize NULL

0040141B |. 6A 00 PUSH 0 ; |pFileSystemNameBuffer NULL

0040141D |. 6A 00 PUSH 0 ; |pFileSystemFlags NULL

0040141F |. 6A 00 PUSH 0 ; |pMaxFilenameLength NULL

00401421 |. 6A 00 PUSH 0 ; |pVolumeSerialNumber NULL

00401423 |. 6A 0B PUSH 0B ; |MaxVolumeNameSize B (11.)

00401425 |. 68 9C334000 PUSH CrackHea.0040339C ; |VolumeNameBuffer CrackHea.0040339C

0040142A |. 6A 00 PUSH 0 ; |RootPathName NULL

0040142C |. E8 A3000000 CALL <JMP.&KERNEL32.GetVolumeInformationA> ; /GetVolumeInformationA

00401431 |. 8D35 9C334000 LEA ESI,DWORD PTR DS:[40339C] ; 把crackme程序所在分区的卷标名称送到ESI

00401437 |. 0FB60D EC334000 MOVZX ECX,BYTE PTR DS:[4033EC] ; 磁盘类型参数送ECX

0040143E |. 33FF XOR EDI,EDI ; 把EDI清零

00401440 |> 8BC1 MOV EAX,ECX ; 磁盘类型参数送EAX

00401442 |. 8B1E MOV EBX,DWORD PTR DS:[ESI] ; 把卷标名作为数值送到 EBX

00401444 |. F7E3 MUL EBX ; 循环递减取磁盘类型参数值与卷标名值相乘

00401446 |. 03F8 ADD EDI,EAX ; 每次计算结果再加上上次计算结果保存在EDI中

00401448 |. 49 DEC ECX ; 把磁盘类型参数作为循环次数，依次递减

00401449 |. 83F9 00 CMP ECX,0 ; 判断是否计算完

0040144C |.^ 75 F2 JNZ SHORT CrackHea.00401440 ; 没完继续

0040144E |. 893D 9C334000 MOV DWORD PTR DS:[40339C],EDI ; 把计算后值送到内存地址40339C，这就是我们后来在ESI中看到的值

00401454 |. 61 POPAD

00401455 /. C3 RETN

通过上面的分析，我们知道基本算法是这样的：先用 GetDriveTypeA 函数获取磁盘类型参数，再用 GetVolumeInformationA 函数获取这个 crackme 程序所在分区的卷标。如我把这个 Crackme 程序放在 F:/OD教程/crackhead/ 目录下，而我 F 盘设置的卷标是 GAME，则这里获取的就是 GAME，ASCII 码为“47414D45”。但我们发现一个问题：假如原来我们在数据窗口中看到的地址 40339C 处的 16 进制代码是“47414D45”，即“GAME”，但经过地址 00401442 处的那条 MOV EBX,DWORD PTR DS:[ESI] 指令后，我们却发现 EBX 中的值是“454D4147”，正好把我们上面那个“47414D45”反过来了。为什么会这样呢？如果大家对 x86系列 CPU 的存储方式了解的话，这里就容易理解了。我们知道“GAME”有四个字节，即 ASCII 码为“47414D45”。我们看一下数据窗口中的情况：

0040339C 47 41 4D 45 00 00 00 00 00 00 00 00 00 00 00 00 GAME............

大家可以看出来内存地址 40339CH 到 40339FH 分别按顺序存放的是 47 41 4D 45。

如下图：

系统存储的原则为“高高低低”，即低字节存放在地址较低的字节单元中，高字节存放在地址较高的字节单元中。比如一个字由两个字节组成，像这样：12 34 ，这里的高字节就是 12 ，低字节就是 34。上面的那条指令 MOV EBX,DWORD PTR DS:[ESI] 等同于 MOV EBX,DWORD PTR DS:[40339C]。注意这里是 DWORD，即“双字”，由 4 个连续的字节构成。而取地址为 40339C 的双字单元中的内容时，我们应该得到的是“454D4147”，即由高字节到低字节顺序的值。因此经过 MOV EBX,DWORD PTR DS:[ESI] 这条指令，就是把从地址 40339C 开始处的值送到 EBX，所以我们得到了“454D4147”。好了，这里弄清楚了，我们再接着谈这个程序的算法。前面我们已经说了取磁盘类型参数做循环次数，再取卷标值 ASCII 码的逆序作为数值，有了这两个值就开始计算了。现在我们把磁盘类型值作为 n，卷标值 ASCII 码的逆序数值作为 a，最后得出的结果作为 b，有这样的计算过程：

第一次：b a * n

第二次：b a * (n - 1) + b

第三次：b a * (n - 2) + b

…

第 n 次：b a * 1 + b

可得出公式为 b a * [n + (n - 1) + (n - 2) + … + 1] a * [n * (n + 1) / 2]

还记得上一篇我们的分析吗？看这一句：

00401405 |. 81F6 53757A79 XOR ESI,797A7553 ; 把ESI中的值与797A7553H异或

这里算出来的 b 最后还要和 797A7553H 异或一下才是真正的注册码。只要你对编程有所了解，这个注册机就很好写了。如果用汇编来写这个注册机的话就更简单了，很多内容可以直接照抄。

到此已经差不多了，最后还有几个东西也说一下吧：

1、上面用到了两个 API 函数，一个是 GetDriveTypeA，还有一个是 GetVolumeInformationA，关于这两个函数的具体用法我就不多说了，大家可以查一下 MSDN。这里只要大家注意函数参数传递的次序，即调用约定。先看一下这里：

00401419 |. 6A 00 PUSH 0 ; /pFileSystemNameSize NULL

0040141B |. 6A 00 PUSH 0 ; |pFileSystemNameBuffer NULL

0040141D |. 6A 00 PUSH 0 ; |pFileSystemFlags NULL

0040141F |. 6A 00 PUSH 0 ; |pMaxFilenameLength NULL

00401421 |. 6A 00 PUSH 0 ; |pVolumeSerialNumber NULL

00401423 |. 6A 0B PUSH 0B ; |MaxVolumeNameSize B (11.)

00401425 |. 68 9C334000 PUSH CrackHea.0040339C ; |VolumeNameBuffer CrackHea.0040339C

0040142A |. 6A 00 PUSH 0 ; |RootPathName NULL

0040142C |. E8 A3000000 CALL <JMP.&KERNEL32.GetVolumeInformationA> ; /GetVolumeInformationA

把上面代码后的 OllyDBG 自动添加的注释与 MSDN 中的函数原型比较一下：

BOOL GetVolumeInformation(

LPCTSTR lpRootPathName, // address of root directory of the file system

LPTSTR lpVolumeNameBuffer, // address of name of the volume

DWORD nVolumeNameSize, // length of lpVolumeNameBuffer

LPDWORD lpVolumeSerialNumber, // address of volume serial number

LPDWORD lpMaximumComponentLength, // address of system\'s maximum filename length

LPDWORD lpFileSystemFlags, // address of file system flags

LPTSTR lpFileSystemNameBuffer, // address of name of file system

DWORD nFileSystemNameSize // length of lpFileSystemNameBuffer

);

大家应该看出来点什么了吧？函数调用是先把最后一个参数压栈，参数压栈顺序是从后往前。这就是一般比较常见的 stdcall 调用约定。

2、我在前面的 00401414 地址处的那条 MOV BYTE PTR DS:[4033EC],AL 指令后加的注释是“磁盘类型参数送内存地址4033EC”。为什么这样写？大家把前一句和这一句合起来看一下：

0040140F |. E8 B4000000 CALL <JMP.&KERNEL32.GetDriveTypeA> ; /GetDriveTypeA

00401414 |. A2 EC334000 MOV BYTE PTR DS:[4033EC],AL ; 磁盘类型参数送内存地址4033EC

地址 0040140F 处的那条指令是调用 GetDriveTypeA 函数，一般函数调用后的返回值都保存在 EAX 中，所以地址 00401414 处的那一句 MOV BYTE PTR DS:[4033EC],AL 就是传递返回值。查一下 MSDN 可以知道 GetDriveTypeA 函数的返回值有这几个：

Value Meaning 返回在EAX中的值

DRIVE_UNKNOWN The drive type cannot be determined. 0

DRIVE_NO_ROOT_DIR The root directory does not exist. 1

DRIVE_REMOVABLE The disk can be removed from the drive. 2

DRIVE_FIXED The disk cannot be removed from the drive. 3

DRIVE_REMOTE The drive is a remote (network) drive. 4

DRIVE_CDROM The drive is a CD-ROM drive. 5

DRIVE_RAMDISK The drive is a RAM disk. 6

上面那个“返回在EAX中的值”是我加的，我这里返回的是 3，即磁盘不可从驱动器上删除。

3、通过分析这个程序的算法，我们发现这个注册算法是有漏洞的。如果我的分区没有卷标的话，则卷标值为 0，最后的注册码就是 797A7553H，即十进制 2038068563。而如果你的卷标和我一样，且磁盘类型一样的话，注册码也会一样，并不能真正做到一机一码。

感谢 mirrormask 兄指出本文中的错误！


第五篇 消息断点及 RUN 跟踪

第一卷 OllyDBG 入门系列 第五篇 消息断点及 RUN 跟踪

作者：CCDebuger

找了几十个不同语言编写的 crackme，发现只用消息断点的话有很多并不能真正到达我们要找的关键位置，想想还是把消息断点和 RUN 跟踪结合在一起讲，更有效一点。关于消息断点的更多内容大家可以参考 jingulong 兄的那篇《几种典型程序Button处理代码的定位》的文章，堪称经典之作。今天仍然选择 crackmes.cjb.net 镜像打包中的一个名称为 cycle 的 crackme。按照惯例，我们先运行一下这个程序看看：

我们输入用户名 CCDebuger，序列号 78787878，点上面那个“Check”按钮，呵， 没反应！看来是要注册码正确才有动静。现在关掉这个 crackme，用 PEiD 查一下壳，原来是 MASM32 / TASM32 [Overlay]。启动 OllyDBG 载入这个程序，F9让它运行。这个程序按我们前面讲的采用字串参考或函数参考的方法都很容易断下来。但我们今天主要学习的是消息断点及 RUN 跟踪，就先用消息断点来断这个程序吧。在设消息断点前，有两个内容我们要简单了解一下：首先我们要了解的是消息。Windows 的中文翻译就是“窗口”，而 Windows 上面的应用程序也都是通过窗口来与用户交互的。现在就有一个问题，应用程序是如何知道用户作了什么样的操作的？这里就要用到消息了。Windows 是个基于消息的系统，它在应用程序开始执行后，为该程序创建一个“消息队列”，用来存放该程序可能创建的各种不同窗口的信息。比如你创建窗口、点击按钮、移动鼠标等等，都是通过消息来完成的。通俗的说，Windows 就像一个中间人，你要干什么事是先通知它，然后它才通过传递消息的方式通知应用程序作出相应的操作。说到这，又有个问题了，在 Windows 下有多个程序都在运行，那我点了某个按钮，或把某个窗口最大化，Windows 知道我是点的哪个吗？这里就要说到另一个内容：句柄（handle）了。句柄一般是个 32 位的数，表示一个对象。Windows 通过使用句柄来标识它代表的对象。比如你点击某个按钮，Windows 就是通过句柄来判断你是点击了那一个按钮，然后发送相应的消息通知程序。说完这些我们再回到我们调试的程序上来，你应该已经用 OllyDBG 把这个 crackme 载入并按 F9 键运行了吧？现在我们输入用户名“CCDebuger”，序列号“78787878”，先不要点那个“Check”按钮，我们来到 OllyDBG 中，点击菜单 查看->窗口（或者点击工具栏上那个“W”的图标），我们会看到以下内容：

我们在选中的条目上点右键，再选择上图所示的菜单项，会来到下面这个窗口：

现在我们点击图上的那个下拉菜单，呵，原来里面的消息真不少。这么多消息我们选哪个呢？注册是个按钮，我们就在按下按钮再松开时让程序中断。查一下 MSDN，我们知道这个消息应该是 WM_LBUTTON_UP，看字面意思也可以知道是左键松开时的消息：

从下拉菜单中选中那个 202 WM_LBUTTON_UP，再按确定按钮，我们的消息断点就设好了。现在我们还要做一件事，就是把 RUN 跟踪打开。有人可能要问，这个 RUN 跟踪是干什么的？简单的说，RUN 跟踪就是把被调试程序执行过的指令保存下来，让你可以查看被调试程序运行期间干了哪些事。RUN 跟踪会把地址、寄存器的内容、消息以及已知的操作数记录到 RUN 跟踪缓冲区中，你可以通过查看 RUN 跟踪的记录来了解程序执行了那些指令。在这还要注意一个缓冲区大小的问题，如果执行的指令太多，缓冲区满了的话，就会自动丢弃前面老的记录。我们可以在调试选项->跟踪中设置：

现在我们回到 OllyDBG 中，点击菜单调试->打开或清除 RUN 跟踪（第一次点这个菜单是打开 RUN 跟踪，在打开的情况下点击就是清除 RUN 跟踪的记录，对 RUN 跟踪熟悉时还可以设置条件），保证当前在我们调试的程序领空，在反汇编窗口中点击右键，在弹出菜单中选择 RUN 跟踪->添加所有函数过程的入口：

我们可以看到 OllyDBG 把识别出的函数过程都在前面加了灰色条：

现在我们回到那个 crackme 中按那个“Check”按钮，被 OllyDBG 断下了：

这时我们点击菜单查看->内存，或者点击工具栏上那个“M”按钮（也可以按组合键 ALT+M），来到内存映射窗口：

为什么在这里设访问断点，我也说一下。我们可以看一下常见的 PE 文件，没加过壳的用 PEiD 检测是这样：

点一下 EP 段后面那个“>”符号，我们可以看到以下内容：

看完上面的图我们应该了解为什么在 401000 处的代码段下访问断点了，我们这里的意思就是在消息断点断下后，只要按 F9 键运行时执行到程序代码段的指令我们就中断，这样就可以回到程序领空了（当然在 401000 处所在的段不是绝对的，我们主要是要看程序的代码段在什么位置，其实在上面图中 OllyDBG 内存窗口的“包含”栏中我们就可以看得很清楚了）。设好访问断点后我们按 F9 键，被 OllyDBG 断下：

现在我们先不管，按 F9 键（或者按 CTR+F12 组合键跟踪步过）让程序运行，再点击菜单查看->RUN 跟踪，或者点击工具栏上的那个“…”符号，打开 RUN 跟踪的记录窗口看看：

我们现在再来看看统计的情况：

在地址 401082 处的那条指令上双击一下，来到以下位置：

现在我们在地址 4010A6 处的那条指令上按 F2，删除所有其它的断点，点菜单调试->关闭 RUN 跟踪，现在我们就可以开始分析了：

004010E2 |. 8BFE MOV EDI,ESI ; 用户名送 EDI

004010E4 |. 03F8 ADD EDI,EAX

004010E6 |. FC CLD

004010E7 |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]

004010E9 |. 33C9 XOR ECX,ECX ; 清零，设循环计数器

004010EB |. BE 71214000 MOV ESI,cycle.00402171 ; 注册码送ESI

004010F0 |> 41 INC ECX

004010F1 |. AC LODS BYTE PTR DS:[ESI] ; 取注册码的每个字符

004010F2 |. 0AC0 OR AL,AL ; 判断是否为空

004010F4 |. 74 0A JE SHORT cycle.00401100 ; 没有则跳走

004010F6 |. 3C 7E CMP AL,7E ; 判断字符是否为非ASCII字符

004010F8 |. 7F 06 JG SHORT cycle.00401100 ; 非ASCII字符跳走

004010FA |. 3C 30 CMP AL,30 ; 看是否小于30H，主要是判断是不是数字或字母等

004010FC |. 72 02 JB SHORT cycle.00401100 ; 小于跳走

004010FE |.^ EB F0 JMP SHORT cycle.004010F0

00401100 |> 83F9 11 CMP ECX,11 ; 比较注册码位数，必须为十进制17位

00401103 |. 75 1A JNZ SHORT cycle.0040111F

00401105 |. E8 E7000000 CALL cycle.004011F1 ; 关键，F7跟进去

0040110A |. B9 01FF0000 MOV ECX,0FF01

0040110F |. 51 PUSH ECX

00401110 |. E8 7B000000 CALL cycle.00401190 ; 关键，跟进去

00401115 |. 83F9 01 CMP ECX,1

00401118 |. 74 06 JE SHORT cycle.00401120

0040111A |> E8 47000000 CALL cycle.00401166 ; 注册失败对话框

0040111F |> C3 RETN

00401120 |> A1 68214000 MOV EAX,DWORD PTR DS:[402168]

00401125 |. 8B1D 6C214000 MOV EBX,DWORD PTR DS:[40216C]

0040112B |. 33C3 XOR EAX,EBX

0040112D |. 3305 82214000 XOR EAX,DWORD PTR DS:[402182]

00401133 |. 0D 40404040 OR EAX,40404040

00401138 |. 25 77777777 AND EAX,77777777

0040113D |. 3305 79214000 XOR EAX,DWORD PTR DS:[402179]

00401143 |. 3305 7D214000 XOR EAX,DWORD PTR DS:[40217D]

00401149 |.^ 75 CF JNZ SHORT cycle.0040111A ; 这里跳走就完蛋

0040114B |. E8 2B000000 CALL cycle.0040117B ; 注册成功对话框

写到这准备跟踪算法时，才发现这个 crackme 还是挺复杂的，具体算法我就不写了，实在没那么多时间详细跟踪。有兴趣的可以跟一下，注册码是17位，用户名采用复制的方式扩展到 16 位，如我输入“CCDebuger”，扩展后就是“CCDebugerCCDebug”。大致是先取扩展后用户名的前 8 位和注册码的前 8 位，把用户名的前四位和后四位分别与注册码的前四位和后四位进行运算，算完后再把扩展后用户名的后 8 位和注册码的后 8 位分两部分，再与前面用户名和注册码的前 8 位计算后的值进行异或计算，最后结果等于 0 就成功。注册码的第 17 位我尚未发现有何用处。对于新手来说，可能这个 crackme 的难度大了一点。没关系，我们主要是学习 OllyDBG 的使用，方法掌握就可以了。

最后说明一下：

1、这个程序在设置了消息断点后可以省略在代码段上设访问断点那一步，直接打开 RUN 跟踪，消息断点断下后按 CTR+F12 组合键让程序执行，RUN 跟踪记录中就可以找到关键地方。

2、对于这个程序，你可以不设消息断点，在输入用户名和注册码后先不按那个“Check”按钮，直接打开 RUN 跟踪，添加“所有函数过程的入口”后再回到程序中点“Check”按钮，这时在 OllyDBG 中打开 RUN 跟踪记录同样可以找到关键位置。


第七篇 汇编功能

第一卷 OllyDBG 入门系列 第七篇 汇编功能

作者：CCDebuger

今天我们的目标程序是 MyUninstaller 1.34 版。这是一个非常小的程序卸载工具，VC6编写，大小只有61K。我拿到的这个是上次闪电狼兄弟给我的，附带在里面的简体中文语言文件是由六芒星制作的。这个程序有个毛病：就是在列出的可卸载程序上双击查看属性时，弹出的属性窗口的字体非常难看，应该就是系统字体（SYSTEM_FONT）：

我们今天的目标就是利用 OllyDBG 的汇编功能把上面显示的字体改成我们常见的9号（小五）宋体。首先我们用 OllyDBG 载入程序，按 CTR+N 组合键查找一下有哪些 API 函数，只发现一个和设置字体相关的 CreateFontIndirectA。现在我们按鼠标右键，选择“在每个参考上设置断点”，关掉名称对话框，F9运行，程序已经运行起来了。我们在程序的列表框中随便找一项双击一下，很不幸，那个字体难看的界面又出现了，OllyDBG 没有任何动作。可见创建这个窗口的时候根本没调用 CreateFontIndirectA，问题现在就变得有点复杂了。先点确定把这个字体难看的对话框关闭，现在我们从另一个方面考虑：既然没有调用设置字体的函数，那我们来看看这个窗口是如何创建的，跟踪窗口创建过程可能会找到一些对我们有用的信息。现在我们再回到我们调试程序的领空，按 CTR+N 看一下，发现 CreateWindowExA 这个 API 函数比较可疑。我们在 CreateWindowExA 函数的每个参考上设上断点，在 MyUninstaller 的列表框中再随便找一项双击一下，被 OllyDBG 断下：

00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; /断在这里

上下翻看一下代码：

00408F3B |. 50 |PUSH EAX ; |hInst

00408F3C |. 8B45 C0 |MOV EAX,DWORD PTR SS:[EBP-40] ; |

00408F3F |. 6A 00 |PUSH 0 ; |hMenu NULL

00408F41 |. 03C6 |ADD EAX,ESI ; |

00408F43 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hParent

00408F46 |. FF75 D0 |PUSH DWORD PTR SS:[EBP-30] ; |Height

00408F49 |. 57 |PUSH EDI ; |Width

00408F4A |. 50 |PUSH EAX ; |Y

00408F4B |. FF75 BC |PUSH DWORD PTR SS:[EBP-44] ; |X

00408F4E |. FF75 EC |PUSH DWORD PTR SS:[EBP-14] ; |Style

00408F51 |. 68 80DE4000 |PUSH myuninst.0040DE80 ; |WindowName ""

00408F56 |. 68 DCD94000 |PUSH myuninst.0040D9DC ; |Class "STATIC"

00408F5B |. FF75 D4 |PUSH DWORD PTR SS:[EBP-2C] ; |ExtStyle

00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; /断在这里

00408F64 | 6A 00 |PUSH 0 ; 第一处要修改的地方

00408F66 | 8945 F4 |MOV DWORD PTR SS:[EBP-C],EAX

00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>

00408F6E |. 50 |PUSH EAX ; |hInst

00408F6F |. 8B45 DC |MOV EAX,DWORD PTR SS:[EBP-24] ; |

00408F72 |. 6A 00 |PUSH 0 ; |hMenu NULL

00408F74 |. 03F0 |ADD ESI,EAX ; |

00408F76 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hParent

00408F79 |. FF75 CC |PUSH DWORD PTR SS:[EBP-34] ; |Height

00408F7C |. 53 |PUSH EBX ; |Width

00408F7D |. 56 |PUSH ESI ; |Y

00408F7E |. FF75 D8 |PUSH DWORD PTR SS:[EBP-28] ; |X

00408F81 |. FF75 E8 |PUSH DWORD PTR SS:[EBP-18] ; |Style

00408F84 |. 68 80DE4000 |PUSH myuninst.0040DE80 ; |WindowName ""

00408F89 |. 68 D4D94000 |PUSH myuninst.0040D9D4 ; |Class "EDIT"

00408F8E |. FF75 B8 |PUSH DWORD PTR SS:[EBP-48] ; |ExtStyle

00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; /CreateWindowExA

00408F97 | 8945 F0 |MOV DWORD PTR SS:[EBP-10],EAX ; 第二处要修改的地方

00408F9A | 8B45 F8 |MOV EAX,DWORD PTR SS:[EBP-8]

00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /%s

00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |

00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format "%s:"

00408FAA |. 50 |PUSH EAX ; |s

00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; /sprintf

00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA

00408FB7 |. 83C4 0C |ADD ESP,0C

00408FBA |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150]

00408FC0 |. 50 |PUSH EAX ; /Text

00408FC1 |. FF75 F4 |PUSH DWORD PTR SS:[EBP-C] ; |hWnd

00408FC4 |. FFD6 |CALL ESI ; /SetWindowTextA

00408FC6 |. 8D85 ACFAFFFF |LEA EAX,DWORD PTR SS:[EBP-554]

00408FCC |. 50 |PUSH EAX ; /Arg3

00408FCD |. FF75 FC |PUSH DWORD PTR SS:[EBP-4] ; |Arg2

00408FD0 |. FF35 00EF4000 |PUSH DWORD PTR DS:[40EF00] ; |Arg1 00BEADCC

00408FD6 |. E8 1884FFFF |CALL <myuninst.sub_4013F3> ; /sub_4013F3

00408FDB |. 83C4 0C |ADD ESP,0C

00408FDE |. 50 |PUSH EAX

00408FDF |. FF75 F0 |PUSH DWORD PTR SS:[EBP-10]

00408FE2 |. FFD6 |CALL ESI

00408FE4 |. FF45 FC |INC DWORD PTR SS:[EBP-4]

00408FE7 |. 8345 F8 14 |ADD DWORD PTR SS:[EBP-8],14

00408FEB |. 837D FC 0F |CMP DWORD PTR SS:[EBP-4],0F

00408FEF |.^ 0F8C 32FFFFFF /JL <myuninst.loc_408F27>

00408FF5 |. 5F POP EDI

00408FF6 |. 5E POP ESI

00408FF7 |. 5B POP EBX

00408FF8 |. C9 LEAVE

00408FF9 /. C3 RETN

我想上面的代码我不需多做解释，OllyDBG 自动给出的注释已经够清楚的了。我们双击 MyUninstaller 列表框中的的某项查看属性时，弹出的属性窗口上的 STATIC 控件和 EDIT 控件都是由 CreateWindowExA 函数创建的，然后再调用 SetWindowTextA 来设置文本，根本没考虑控件上字体显示的问题，所以我们看到的都是系统默认的字体。我们要设置控件上的字体，可以考虑在 CreateWindowExA 创建完控件后，在使用 SetWindowTextA 函数设置文本之前调用相关字体创建函数来选择字体，再调用 SendMessageA 函数发送 WM_SETFONT 消息来设置控件字体。

思路定下来后，我们就开始来实施。首先我们看一下这个程序中的导入函数，CreateFontIndirectA 这个字体创建函数已经有了，再看看 SendMessageA，呵呵，不错，原程序也有这个函数。这样我们就省事了。

有人可能要问，如果原来并没有这两个导入函数，那怎么办呢？其实这也很简单，我们可以直接用 LordPE 来在程序中添加我们需要的导入函数。我这里用个很小的 PE 工具 zeroadd 来示范一下，这个程序里面没有 CreateFontIndirectA 和 SendMessageA 函数（这里还有个问题说一下，其实我们编程时调用这两个函数时都是直接写 CreateFontIndirect 及 SendMessage，一般不需指定。但在程序中写补丁代码时我们要指定这是什么类型的函数。这里在函数后面加个“A”表示这是 ASCII 版本，同样 UNICODE 版本在后面加个“W”，如 SendMessageW。在 Win9X 下我们一般都用 ASCII 版本的函数，UNICODE 版本的函数很多在 Win9X 下是不能运行的。而NT 系统如 WinXP 一般都是 UNICODE 版本的，但如果我们用了 ASCII 版本的函数，系统会自动转换调用 UNICODE 版本。这样我们写补丁代码的时候就可以直接指定为 ASCII 版本的函数，可以兼容各个系统）。

我们用 LordPE 的 PE 编辑器载入 zeroadd 程序，选择“目录”，再在弹出的目录表对话框中选择输入表后面的那个“...”按钮，会弹出一个对话框：

因为 SendMessageA 在 USER32.dll 中，我们在右键菜单中点击按钮“添加导入表”，来到下面：

按上面的提示完成后点“确定”，我们回到原先的那个“输入表”对话框：

从上图中我们可以看出多出了一个 USER32.dll，这就是我们添加 SendMessageA 的结果。这也是用工具添加的一个缺点。我们一般希望把添加的函数直接放到已存在的 DLL 中，而不是多出来一个，这样显得不好看。但用工具就没办法，LordPE 默认是建一个 1K 的新区段来保存添加后的结果，由此出现了上图中的情况。如果你对 PE 结构比较熟悉的话，也可以直接用 16进制编辑工具来添加你需要的函数，这样改出来的东西好看。如果想偷懒，就像我一样用工具吧，呵呵。

在上图中我还标出了要注意 FirstThunk 及那个 ThunkRVA 的值，并且要把“总是查看 FirstThunk”那个选项选上。有人可能不理解其作用，我这里也解释一下：一般讲述 PE 格式的文章中对 FirstThunk 的解释是这样的：FirstThunk 包含指向一个 IMAGE_THUNK_DATA 结构数组的 RVA 偏移地址，当把 PE 文件装载到内存中时，PE装载器将查找 IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 这些结构数组来决定导入函数的地址，随后用导入函数真实地址来替代由 FirstThunk 指向的 IMAGE_THUNK_DATA 数组里的元素值。

这样说起来还是让人不明白，我举个例子：比如你有个很要好的朋友，他是个大忙人，虽然你知道他的家庭住址，可他很少回家。如果你哪天想找他，直接去他家，很可能吃个闭门羹，找不到他人。怎么办？幸好你有他的手机号码，你就给他拨了一个电话：“小子，你在哪呢？”，他告诉你：“我正在XXX饭店喝酒呢！”这时你怎么办？（当然是杀到他说的那家饭店去蹭饭了！^_^）这里的 ThunkRVA 就相当于你朋友的手机号码， SendMessageA 就相当于你那个朋友。而 FirstThunk 就是你手机里的号码分组。你把你的多个朋友都放在 FirstThunk 这样的号码分组里，每个 ThunkRVA 就是你一个朋友的手机号码。你要找他们，就是通过 ThunkRVA 这样的手机号码来和他们联系，直接去他家找他你很可能要碰壁。而移动或联通就相当于操作系统，他们负责把你的手机号码和你的朋友对应上。而 FirstThunk 这样的号码分组还有一个好处就是你可以不记你某个朋友的具体号码，只要记得 FirstThunk 号码分组的值，你的朋友会按顺序在里面排列。比如上图中 USER32.dll 中的第一个函数是 SendMessageA，它的 ThunkRVA 值就是 FirstThunk 值。如果还有第二个函数，比如是 MessageBoxA，它的值就是 FirstThunk 值加上 4，其余类推。你只要记住各个函数的位置，也可以通过 FirstThunk 加上位置对应值来找到它。当然这比不上直接看 ThunkRVA 来得方便。

说了上面这些，我们就要考虑怎么在程序中调用了。你可能会说，我在 OllyDBG 中直接在我们要修改的程序中这样调用：CALL SendMessageA。哦，别这样。这等于我上面说的都是废话，会让我感到伤心的。你这里的 CALL SendMessageA 就相当于也不跟你朋友打个招呼就直接去他家找他，很有可能你会乘兴而去，败兴而归。别忘了他的手机号码，我们只有通过号码才知道他到底在什么地方。我们应该这样：CALL DWORD PTR [40B01A]，这里的 40B01A 就是上面的 SendMessageA 在程序载入后的所在的地方，由基址 00400000 加上 ThunkRVA 0000B01A 得到的。这就是你要找的人所在的地方，不管他跑到哪，你有他的手机号码就能找到他。同样道理，你只要记住了 ThunkRVA 值，就按这个来调用你需要的函数，在别的 Windows 系统下也是没有问题的。系统会自动把你要找到函数和 ThunkRVA 值对应上。而你在 OllyDBG 中写 CALL SendMessageA，可能你在你的系统上成功了，可放到别的系统下就要出错了。为什么？因为你找的人已经不在原来的位置了，他跑到别的地方去了。你还到老地方找他，当然看不见人了。

说了这么多废话，也不知大家听明白了没有，别越听越糊涂就行了。总之一句话，别像 CALL SendMessageA 这样直接调用某个函数，而应该通过 ThunkRVA 值来调用它。

下面我们回到我们要修改的 MyUninstaller 上来，先用 LordPE 打开看一下，呵呵，原来 CreateFontIndirectA 和 SendMessageA 原程序里面都有了，省了我们不少事情。看一下这两个函数的 ThunkRVA 值，CreateFontIndirectA 在 GDI32.dll 里面，ThunkRVA 值是 0000B044，这样我们就知道在程序中调用它的时候就是 CALL DWORD PTR [0040B044]。同样，SendMessageA 的ThunkRVA 值是 0000B23C，调用时应该是这样：CALL DWORD PTR [0040B23C]。了解了这些东西我们就来考虑怎么写代码了。首先我们来看一下 CreateFontIndirectA 和 SendMessageA 这两个函数的定义：

CreateFontIndirectA：

HFONT CreateFontIndirect(

CONST LOGFONT *lplf // pointer to logical font structure

);

CreateFontIndirect的返回值就是字体的句柄。

对于这个函数我们需要的参数就是给它一个 LOGFONT 的字体结构指针，我们只要在要修改程序的空白处建一个标准的9号（小五）宋体的 LOGFONT 字体结构，再把指针给 CreateFontIndirectA 就可以了。

SendMessageA：

LRESULT SendMessage(

HWND hWnd, // handle of destination window

UINT Msg, // message to send

WPARAM wParam, // first message parameter

LPARAM lParam // second message parameter

);

上面的第一个参数是窗口句柄，我们知道 CreateWindowExA 返回的就是窗口句柄，我们可以直接拿来用。第二个消息参数我们这里是设置字体，选WM_SETFONT，这个值是 30H。第三个参数是字体句柄，可以由上面的 CreateFontIndirectA 获得。第四个参数我们不需要，留空。现在我们准备开始写代码，首先我们要在程序中建一个标准9号宋体的 LOGFONT，以便于我们调用。对于 LOGFONT，我们再来看一下定义：

typedef struct tagLOGFONT { // lf

LONG lfHeight;

LONG lfWidth;

LONG lfEscapement;

LONG lfOrientation;

LONG lfWeight;

BYTE lfItalic;

BYTE lfUnderline;

BYTE lfStrikeOut;

BYTE lfCharSet;

BYTE lfOutPrecision;

BYTE lfClipPrecision;

BYTE lfQuality;

BYTE lfPitchAndFamily;

TCHAR lfFaceName[LF_FACESIZE];

} LOGFONT;

这样我们的标准9号宋体的 LOGFONT 值应该是32字节，16进制就像这样：F4FFFFFF000000000000000000000000900100000000008600000000CBCECCE5。现在在程序中找个空地。我们用 PEiD 来帮助我们寻找，用 PEiD 打开程序，点 EP 段后面的那个 > 号，随便选择一个区段右击，选“搜索全0处”（原版好像是cave什么的）：

我们看到 PEiD 把搜索到的空间都给我们列出来了：

现在我们用 WinHEX 打开我们要修改的程序，转到偏移 9815 处，从 9815 处选择 32 字节（16进制是0X20）的一个选块，把光标定位到 9815 处，右键选择菜单 剪贴板数据->写入(从当前位置覆写)，随后的格式选择 ASCII Hex，把我们 LOGFONT 的 16 进制值

F4FFFFFF000000000000000000000000900100000000008600000000CBCECCE5

写入保存。现在我们用 OllyDBG 载入已添加了 LOGFONT 数据的程序，先转到 VA 40A415 处（从上图中看到的）往下看一下：

因为 SendMessageA 还要用到一个窗口句柄，我们可以通过前面的 CreateWindowExA 来获得。现在我们就把前一张图中的 .rdata 区段中的地址 0040C56E 作为我们保存窗口句柄 HWND 值的临时空间。一切就绪，开始写代码。先回顾一下我们最先说的那两个要修改的地方：

第一个要改的地方：

00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; /CreateWindowExA

00408F64 6A 00 PUSH 0 ; 修改前

00408F66 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX

00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>

修改后：

00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; /CreateWindowExA

00408F64 E9 D5140000 JMP myuninst.0040A43E ; 跳转到我们的补丁代码处

00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>

第二个要改的地方：

00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; /CreateWindowExA

00408F97 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX ; 改这里

00408F9A 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]

00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /%s

00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |

00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format "%s:"

00408FAA |. 50 |PUSH EAX ; |s

00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; /sprintf

00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA

修改后：

00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; /CreateWindowExA

00408F97 E9 D4140000 JMP myuninst.0040A470 ; 跳到我们的第二部分补丁代码处

00408F9C 90 NOP

00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /%s

00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |

00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format "%s:"

00408FAA |. 50 |PUSH EAX ; |s

00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; /sprintf

00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA

这两个地方的修改都是把原代码改成跳转，跳到我们的补丁代码那继续执行。在修改之前先把原代码复制下来，以便恢复。我们在 OllyDBG 中按 CTR+G 组合键，来到 0040A43E 地址处，开始输补丁代码：

同样，我们也在 0040A470 地址处输入我们另一部分的补丁代码。两部分的补丁代码分别如下：

补丁代码1：

0040A43E 60 PUSHAD ; 保护现场

0040A43F A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX ; 保存窗口句柄

0040A444 68 15A44000 PUSH myuninst.0040A415 ; 传递字体句柄LOGFONT

0040A449 FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; GDI32.CreateFontIndirectA

0040A44F 6A 00 PUSH 0 ; lParam 参数留空

0040A451 50 PUSH EAX ; 字体句柄LOGFONT

0040A452 6A 30 PUSH 30 ; WM_SETFONT

0040A454 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; 窗口句柄送ECX

0040A45A 51 PUSH ECX ; 压入窗口句柄参数

0040A45B FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; USER32.SendMessageA

0040A461 61 POPAD ; 恢复现场

0040A462 6A 00 PUSH 0 ; 恢复原代码

0040A464 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX

0040A467 ^ E9 FDEAFFFF JMP myuninst.00408F69 ; 返回

补丁代码2：

0040A470 > /60 PUSHAD

0040A471 . A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX

0040A476 . 68 15A44000 PUSH myuninst.0040A415 ; /pLogfont myuninst.0040A415

0040A47B . FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; /CreateFontIndirectA

0040A481 . 6A 00 PUSH 0 ; /lParam 0

0040A483 . 50 PUSH EAX ; |wParam

0040A484 . 6A 30 PUSH 30 ; |Message WM_SETFONT

0040A486 . 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; |

0040A48C . 51 PUSH ECX ; |hWnd > NULL

0040A48D . FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; /SendMessageA

0040A493 . 61 POPAD

0040A494 . 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX

0040A497 . 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]

0040A49A .^ E9 FEEAFFFF JMP myuninst.00408F9D

补丁代码2因为与补丁代码1类似，我就不做详细解释了。现在我们的代码都写完了，现在我们开始保存我们的工作，选中我们修改的代码，点击鼠标右键，会出来一个菜单：

我们左键选所有修改（当然选它了，要不然只会保存我们选定的这一部分。关于这个地方还要说一下，有的时候我们修改完程序选“复制到可执行文件”时只有“选择”菜单，没有“所有修改”菜单项。按 OllyDBG 帮助里关于备份功能的说法，好像是受内存块限制的，补丁功能也同样是这样。对于备份及补丁功能我用的比较少，并不是很了解，这方面的内容还是大家自己去研究吧，有什么好的心得也希望能共享一下。我遇到不能保存所有修改的情况就是先把补丁代码全部复制下来，同时利用二进制功能复制代码，先选一段补丁代码保存为文件，再用 OllyDBG 打开保存后的文件，转到相应位置分别把我们复制下来的补丁二进制代码粘贴上去后保存。纯属笨办法，当然你也可以用 HexView 这样的工具来修改代码），随后会出来一个“把选中的内容复制到可执行文件”的对话框，我们选“全部复制”，又出来一个对话框，我们在上面点右键，在弹出的菜单上选“保存文件”：

这时会出来一个另存文件的对话框，我们另选一个名字如 myuninst1.exe 来保存，不要直接覆盖原文件 myuninst.exe,以便于出错后好修改。现在关闭 OllyDBG，先不要急着运行刚刚修改过的文件，因为我们还有个地方要改一下。大家还记得我们在 .rdata 中用了个地方作为我们保存临时变量的地方吧？原先的 .rdata 段属性设置是不可写的，现在我们写入了数据，运行时是会出错的。现在我们要修改一下 .rdata 段的属性。用 LordPE 的 PE 编辑器打开我们修改后的程序，点“区段”按钮，在弹出的对话框中点击 .rdata 段，右键选择弹出菜单中的“编辑区段”：

在弹出的对话框中选标志后面那个“...”按钮：

现在我们把区段标志添加一个可写入的属性：

完成后按确定保存我们所做的工作，运行一下修改后的程序，呵呵，终于把字体改过来了：

如果你运行出错也没关系，用 OllyDBG 调试一下你修改后的程序，看看错在什么地方。这一般都是输入补丁代码时造成的，你只要看一下你补丁代码运行的情况就可以了。到这里我们的任务似乎也完成了，但细心的朋友可能会发现补丁代码1和补丁代码2前面的代码基本上是相同的。一个两个这样的补丁还好，如果要是多的话，这样重复就要浪费不少空间了，况且工作量也相应加大了。既然前面有很多代码都是重复的，为什么我们不把这些重复的代码做成一个子程序呢？这样调用起来要方便的多。下面我们把前面的补丁代码修改一下，我们先把补丁代码1的代码改成这样：

0040A43E 60 PUSHAD ; 保护现场

0040A43F A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX ; 保存窗口句柄

0040A444 68 15A44000 PUSH myuninst.0040A415 ; 我们建的LOGFONT对应指针

0040A449 FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; GDI32.CreateFontIndirectA

0040A44F 6A 00 PUSH 0 ; lParam 参数留空

0040A451 50 PUSH EAX ; 字体句柄

0040A452 6A 30 PUSH 30 ; WM_SETFONT

0040A454 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; 窗口句柄

0040A45A 51 PUSH ECX ; 窗口句柄压栈

0040A45B FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; USER32.SendMessageA

0040A461 61 POPAD ; 恢复现场

0040A462 C3 RETN ; 返回

这样我们的子程序代码就写好了。现在我们再在子程序代码后面写上两个补丁代码，当然不要忘了改前面原程序中的跳转：

修改后的补丁代码1：

0040A467 E8 D2FFFFFF CALL myuninst.0040A43E ; 调用子程序

0040A46C 6A 00 PUSH 0 ; 恢复前面修改过的代码

0040A46E 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX

0040A471 ^ E9 F3EAFFFF JMP myuninst.00408F69 ; 返回继续执行

修改后的补丁代码2：

0040A47A E8 BFFFFFFF CALL myuninst.0040A43E

0040A47F 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX

0040A482 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]

0040A485 ^ E9 13EBFFFF JMP myuninst.00408F9D

我在每个补丁代码片断间留了4个字节来分隔。同样，我们还要修改一下我们前面的跳转：

第一个要修改跳转的地方：

00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; /断在这里

00408F64 E9 FE140000 JMP myuninst.0040A467 ; 跳到我们的第一部分补丁代码处

00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>

第二个要修改跳转的地方：

00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; /CreateWindowExA

00408F97 E9 DE140000 JMP myuninst.0040A47A ; 跳到我们的第二部分补丁代码处

00408F9C 90 NOP

00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /%s

修改好后保存，同样不要忘了再修改一下 .rdata 区段的属性。运行一下，一切OK！


第一篇 OllyDbg实用技巧六则

第二卷 OllyDBG 技巧系列 第一篇 OllyDbg实用技巧六则

作者：doskey

1、让跳转路径显示出来

打开Options/Debugging Option。弹出Debugging Option对话框，选择CPU页，选定“Show direction to jumps”、“Show jump path”和“Show grayed path if jump isnot taken”。如此以来在Disassembler窗口就会显示跳转的路径了。

2、让OD显示MFC42.DLL中的函数

如果程序是用MFC进行的动态编译，那么在OD中将只能显示MFC42.DLL中的函数为：

00410E40 |. E8 43000000 CALL <JMP.&MFC42.#1576>

1576是函数在MFC42.DLL中的序号。打开Debug/Select import libraries，单击弹出的对话框中“Add”，在弹出的打开文件对话框中选择“MFC42.LIB”并打开，重新载入MFC程序，你就可以看见函数名称变为：

00410E40 |. E8 43000000 CALL <JMP.&MFC42.#1576_?AfxWinMain@@Y>

IDA中分析出了来的东西一样了！呵呵，以后不用等待IDA的“细嚼慢咽”也可以轻松搞定MFC程序了。其他的DLL类似，如果有序号，可以在VC的LIB目录中找到相关的.LIB文件，加到OD中便可。如果你没有“MFC42.DLL”，你可以的到新论坛的下载区找，我已经上传到那里了。

3、让OD轻松躲过“ANTI-DEBUG”

很多“ANTI-DEBUG”的程序都是在程序开始时来检查是否安装调试器的。用这种特性我们可以轻松的用OD的“Attach”绕过检查部分。如“X语言”，如果你用TRW2K/S-ICE/OD直接加载它的话，程序回警告你安装了调试器并结束。但是我们在“X语言”开启后再运行OD，并用“Attach”系上它就就可以了，轻松通过检查。而且在OD系上它后仍然可以用CTRL+A进行分析。如此一来，快哉！：）

4、轻松对付调用“MessageBoxA”以及类似的模态对话框的程序

很多人都认为OD不好拦截“MessageBoxA”这类API函数。其实我们有个很简单的办法将API拦截下来，并且快速找到比较地点/主算法地点。首先用OD加载目标程序，如果不能加载，用上面的方法“Attach”目标程序。然后，F9运行目标程序，并且有意让目标程序显示“MessageBox”，然后切换到OD中，F12暂停，如

0041201F |> 53 PUSH EBX ; /Style

00412020 |. 57 PUSH EDI ; |Title

00412021 |. FF75 08 PUSH [ARG.1] ; |Text

00412024 |. FF75 F4 PUSH [LOCAL.3] ; |hOwner

00412027 |. FF15 A8534100 CALL DWORD PTR DS:[4153A8] ; /MessageBoxA

0041202D |. 85F6 TEST ESI, ESI ; 停在此处

0041202F |. 8BF8 MOV EDI, EAX

00412031 |. 74 05 JE SHORT 1551-CRA.00412038

F8单步一下，切换到“MessageBox”中，确认，被OD中断。我们可以看见上面的代码41201F处有一个“〉”，说明可以从某段代码跳转到此处，我们选择41201F这一行，在“Information”栏看见一句“JUMP FROM 412003”，右键单击，选择“GO TO JUMP FROM412003”。回到412003，一般都是条件跳转，上面的内容就是比较的地方啦。：）

5、使用OD的TRACK功能

OD拥有强大的TRACK功能，在分析算法时十分有用。首先我们要设定OD的TRACK缓冲区大小，选择Option/Debugging Option，在弹出的对话框中选择TRACK页，“Size of run track buffer(byte/record)”，缓冲区大小，当然约大约好。其他的设置在我以前的OLLYDBG.INI中都已经设置好了。然后，开启目标程序，在DEBUG中选择“Openor clear run track”。然后我们就可以用CTRL+F11或CTRL+F12开启“Track into”和“Track over”了。当我们暂停程序的时候，可以用小键盘上的“+”，“-”，“*”来控制TRACK功能了。

“Track into”和运行类似，但是记录所有指令以及寄存器变化。并且会自动进入所有的CALL中。

“Track over”和“Track into”类似，但是不进入CALL

“+”用来显示TRACK缓冲区中的下一条指令

“-”用来显示TRACK缓冲区中的上一条指令

“*”用来发返回当前指令

6、不是技巧的技巧

当你遇到花指令的时候一定会很头痛。但是如果你用OD进行分析的时候就会轻松得多。OD会自动标识出无效指令，即花指令。如果OD没有正确识别，你还可以用CTRL+↑/↓来单个字节的移动。可以很有效的识别出花指令的所在。

后记

本人脑袋不太好使，一时记不起所有内容，以后想起来再贴。转贴时请保持本文的完整。


第二篇 OllyDbg之万能断点篇

第二卷 OllyDBG 技巧系列 第二篇 OllyDbg之万能断点篇

作者：txm123

终于有一点时间总结一下OD破解的经验,有许多大侠们都认为OD不能下万能断点,可是有些软件的破解需要用到万能断 点,以前一直没有这方面的资料,有的只是OD不能下万能断点的断言,一时心恢之极,可是我原来用TRW和SICE用的挺好,谁知 系统又跟我闹矛盾,一用TRW和SICE就死机,没办法我现在只能用OD,我写的一些破解文章都是用OD破的,反复试验,我终于发 现OD也能下万能断点,在我以前写的一些文章中好象有一篇简单提到过,但没有说的很详细,这次静下心,想写一点东西,送给 我心爱的组织DFCG吧,我的破解是从DFCG组织起步的,很感谢DFCG的高手们的帮助,在此说声谢谢!为了更明确,这次破解所使用的调试器OD是从看雪论坛临时下载的OD1.09的汉化版,没有使用我自己手头经过改造的OD,在此也感谢看雪论坛,这里真的有很多高手,在这个论坛我也学到了很多.这次破解的对象是超级XX王,为保护国产软件我隐去了软件相关信息,我不想给自己带来麻烦,现在工作很忙,能少一点麻烦就尽量少一点吧.

破解作者

yzez[DFCG]

破解工具

ollydbg1.09汉化版,看雪论坛下载.

破解目的

本不为破解而破解,只因为技术而破解

破解环境

WINDOWS XP,这个我已经在98和XP下各做了两次,贴图和我这篇文章是在XP系统下完成的.

破解过程

1.检查了一下,该程序无壳,C++编译,用W32DSM反汇编一堆乱码,我倒!那就动态调试吧!用ollydbg1.09载入程序,这次我将教你们如何用OD下万能断点,过程我尽可能详细一点,下面请看.载入程序选OD菜单栏上的插件-----命令行(快捷键是ALT+F1),在弹出的窗口中输入万能断点命令:bpx hmemcpy,按ENTER键,结果又出现一个对话框:

Intermodular calls

00401164 CALL DWORD PTR DS:[<&USER32.GetWindowRect>] USER32.GetWindowRect

0040118F CALL DWORD PTR DS:[<&USER32.GetClientRect>] USER32.GetClientRect

00401279 CALL DWORD PTR DS:[<&USER32.GetClientRect>] USER32.GetClientRect

004013E1 CALL DWORD PTR DS:[<&USER32.GetClientRect>] USER32.GetClientRect

0040190A CALL DWORD PTR DS:[<&KERNEL32.GetThreadLocale>] kernel32.GetThreadLocale

0040191C CALL DWORD PTR DS:[<&KERNEL32.GetLocaleInfoA>] kernel32.GetLocaleInfoA

还有很多,我就省略了,在这个框里右键单击,出现一个对话框,选中在每个命令中设置断点(热键是S),单击,你看每一行都变成了红色吧,这表示全部设下断点了,后面的过程很烦燥,你要一边按F9,边按F2把一些无用的断点去掉,有一点耐心吧,当然也可以不这样做,但遇到断点跳不过的时候,你就得按F2把这个断点去掉.

感觉在这一点上OD比不上TRW和SICE,到出现注册信息框后,你要输入注册信息,然后按确定,程序被断下来,我的序列号是:

GK342QZ0C6RE03L,我输入试验码:123456789098765.程序中断在下面:

00471F71 CALL DWORD PTR DS:[<&USER32.GetWindowTex>****按确定后回到OD程序中断在此,按F8走,在此还没有到关键处!

*****************************************************注意这里会循环两次,分别处理序列号和试验码,按F8走

00471F77 LEA ECX,DWORD PTR DS:[EAX+1]

00471F7A PUSH ECX

00471F7B MOV ECX,DWORD PTR SS:[EBP+10]

00471F7E PUSH EAX

00471F7F CALL SuperPIM.0043E15D

00471F84 PUSH EAX

00471F85 PUSH ESI

00471F86 CALL DWORD PTR DS:[<&USER32.GetWindowTex>

00471F8C MOV ECX,DWORD PTR SS:[EBP+10]

00471F8F PUSH -1

00471F91 CALL SuperPIM.00401D6A

00471F96 JMP SHORT SuperPIM.00471FA3

00471F98 MOV EAX,DWORD PTR SS:[EBP+10]

00471F9B PUSH DWORD PTR DS:[EAX]

00471F9D PUSH ESI

00471F9E CALL SuperPIM.00470B61

00471FA4 POP ESI

00471FA5 POP EBP

00471FA6 RETN 0C*************************************程序第二次循环后最后会返回到0040FE99下面看代码:

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

0040FE99 LEA EAX,DWORD PTR SS:[EBP-14]****第二次循环后会返回到这里,注意这就是我们要找的关键地方,按F8往下!

0040FE9C PUSH EAX

0040FE9D CALL SuperPIM.00433D92***********这就是关键CALL,按F7追进,一定要进,因为算法就在这里面!

0040FEA2 POP ECX

0040FEA3 AND DWORD PTR SS:[EBP-4],0

0040FEA7 LEA ESI,DWORD PTR DS:[EDI+74]

0040FEAA MOV EAX,DWORD PTR DS:[ESI]******试验码入EAX

0040FEAC CMP DWORD PTR DS:[EAX-C],0******比较试验码输入了吗?

0040FEB0 JE SHORT SuperPIM.0040FF2F******没有输入就跳走,一跳就失败!

0040FEB2 PUSH 28

0040FEB4 LEA EAX,DWORD PTR SS:[EBP-18]

0040FEB7 PUSH ESI

0040FEB8 PUSH EAX

0040FEB9 CALL SuperPIM.0042CEC8**********此CALL对输入的注册码进行处理,得到一个40位的长字符串,有兴趣自己跟,******************************************我已是头晕的很,不想跟进!

0040FEBE LEA ECX,DWORD PTR SS:[EBP-14]

0040FEC1 PUSH ECX

0040FEC2 PUSH EAX

0040FEC3 CALL SuperPIM.0040F9B7**********此CALL对序列号处理,也是得到一个40位的长字符串

0040FEC8 MOV ECX,DWORD PTR SS:[EBP-18]

0040FECB ADD ESP,14

0040FECE ADD ECX,-10

0040FED1 MOV BYTE PTR SS:[EBP-D],AL

0040FED4 CALL SuperPIM.00401B5D**********此CALL进行比较,注册码不对,值为0

0040FED9 CMP BYTE PTR SS:[EBP-D],0*******比较是0吗?

0040FEDD JE SHORT SuperPIM.0040FF2F******相等就跳,跳就失败,所以一定不能跳!

0040FEDF PUSH 0**************************不跳往下你就成功了!下面代码省略!

...............................................................省略若干代码!.........................

************************************关键CALL的代码!*******************************************************

00433D92 MOV EAX, SuperPIM.0049844E****追进关键CALL后我们来到这里!

00433D97 CALL SuperPIM.00450900

00433D9C PUSH ECX

00433D9D PUSH ECX

00433D9E AND [LOCAL.5], 0

00433DA2 LEA EAX, [LOCAL.5]

00433DA5 PUSH EAX

00433DA6 CALL SuperPIM.00433CA9********此CALL根据电脑硬件信息得到你的序列号,有兴趣跟吧!我是没有兴趣!

00433DAB AND [LOCAL.1], 0

00433DAF PUSH 14

00433DB1 LEA EAX, [LOCAL.5]

00433DB4 PUSH EAX

00433DB5 LEA EAX, [LOCAL.4]

00433DB8 PUSH EAX

00433DB9 CALL SuperPIM.0042CEC8*******算法CALL(1),按F7跟进!

00433DBE PUSH 28

00433DC0 LEA EAX, [LOCAL.4]

00433DC3 PUSH EAX

00433DC4 PUSH [ARG.1]

00433DC7 MOV BYTE PTR SS:[EBP-4], 1

00433DCB CALL SuperPIM.0042CEC8

00433DD0 MOV ECX, [LOCAL.4]

00433DD3 ADD ESP, 1C

00433DD6 ADD ECX, -10

*****************************省略N行代码!********************************************************

0043454B RETN

***********************************算法CALL!*********************************************************

0042CEC8 MOV EAX, SuperPIM.00497B52********追进算法CALL我们在这里!

0042CECD CALL SuperPIM.00450900

0042CED2 SUB ESP, 14

0042CED5 PUSH EBX

0042CED6 PUSH ESI

0042CED7 XOR EBX, EBX

0042CED9 PUSH EDI

0042CEDA MOV [LOCAL.8], EBX

0042CEDD CALL SuperPIM.004639B5

0042CEE2 MOV EDX, DWORD PTR DS:[EAX]

0042CEE4 MOV ECX, EAX

0042CEE6 CALL DWORD PTR DS:[EDX+C]

0042CEE9 LEA EDI, DWORD PTR DS:[EAX+10]

0042CEEC MOV [LOCAL.4], EDI

0042CEEF MOV EAX, [ARG.2]

0042CEF2 MOV EAX, DWORD PTR DS:[EAX]*******序列号:GK342QZ0C6RE03L移入EAX

0042CEF4 MOV ESI, DWORD PTR DS:[EAX-C]*****序列号的位数15(十六进制值是F)送入ESI

0042CEF7 CMP ESI, EBX**********************比较ESI和EBX,ESI的值是F即序列号位数,EBX的初始值是0

0042CEF9 MOV [LOCAL.1], EBX

0042CEFC MOV [LOCAL.8], ESI

0042CEFF JE SuperPIM.0042CFB2*************相等就跳,这里当然不相等,所以就不会跳!

0042CF05 MOV EAX, [ARG.3]******************常数14(十进制值是20)送入EAX

0042CF08 CMP EAX, ESI**********************比较EAX和ESI,即14和F比较!

0042CF0A MOV [LOCAL.6], EAX

0042CF0D JG SHORT SuperPIM.0042CF12*******大于就跳!

0042CF0F MOV [LOCAL.6], ESI

0042CF12 CMP [LOCAL.6], EBX****************跳到这里!比较14和0

0042CF15 JLE SuperPIM.0042CFB2*************小于就跳,这里当然不会小!所以不跳!

0042CF1B MOV EAX, EBX**********************0移入EAX

0042CF1D CDQ***********************************EDX清0,准备计算!

0042CF1E IDIV ESI***************************除,EAX/ESI ,EAX的值是0,ESI的值存放序列号的位数F

0042CF20 MOV EAX, [ARG.2]******************赋EAX地址值

0042CF23 MOV EAX, DWORD PTR DS:[EAX]*******序列号:GK342QZ0C6RE03L移入EAX

0042CF25 PUSH EBX***************************EBX入栈

0042CF26 MOV AL, BYTE PTR DS:[EDX+EAX]*****序列号的第一位G(ASCII码值47)入AL

0042CF29 MOV BYTE PTR SS:[EBP-1C], AL******保存值47

0042CF2C PUSH [LOCAL.7]

0042CF2F CALL SuperPIM.0042CD8E*************算法CALL(2),按F7跟进!代码直接在下面给出

***************************算法CALL(2)*****************************************************

0042CD8E PUSH EBP

0042CD8F MOV EBP, ESP

0042CD91 PUSH ECX*************************G的ASCII码值47入ECX

0042CD92 MOVZX EAX, BYTE PTR SS:[EBP+8]****扩展成00000047

0042CD96 MOV ECX, [ARG.2]****************赋ECX的初始值为0

0042CD99 MOV [LOCAL.1], EAX

0042CD9C MOV EAX, ECX********************ECX的值移入EAX

0042CD9E IMUL EAX, ECX********************EAXEAX*ECX0*0

0042CDA1 LEA EAX, DWORD PTR DS:[EAX+EAX*2+7]**EAX+EAX*2+7的值7给EAX

0042CDA5 IMUL EAX, ECX********************EAXEAX*ECX7*0

0042CDA8 ADD EAX, 0D*********************EAXEAX+0DD

0042CDAB IMUL EAX, ECX********************EAXEAX*ECXD*00

0042CDAE PUSH ESI*************************序列号位数F入栈!

0042CDAF LEA ESI, DWORD PTR DS:[ECX+5]

0042CDB2 PUSH ESI

0042CDB3 MOV [ARG.1], EAX

0042CDB6 LEA EAX, [ARG.1]

0042CDB9 PUSH 4

0042CDBB PUSH EAX

0042CDBC CALL SuperPIM.0042CD2E***********这个CALL也在计算,我实在不想跟进!

0042CDC1 PUSH ESI

0042CDC2 LEA EAX, [LOCAL.1]

0042CDC5 PUSH 4

0042CDC7 PUSH EAX

0042CDC8 CALL SuperPIM.0042CD5E**********这个CALL也是计算CALL,烦!不跟了!

0042CDCD MOV EAX, [ARG.1]

0042CDD0 ADD ESP, 18

0042CDD3 XOR EAX, [LOCAL.1]*************这里赋EAX的值38000002

0042CDD6 POP ESI

0042CDD7 LEAVE

0042CDD8 RETN

算法CALL(2)结束

0042CF34 POP ECX

0042CF35 POP ECX

0042CF36 PUSH 2B***************************常数2B入栈!

0042CF38 XOR EDX, EDX

0042CF3A POP ECX**************************把常数2B赋给ECX

0042CF3B DIV ECX**************************除,EAX/ECX38000002/2B014D6535,余数1B入EDX

0042CF3D MOV ECX, EDX*********************结果1B入ECX

0042CF3F ADD CL, 30***********************CLCL+301B+304B(对应的字符串是K)

0042CF42 CMP CL, 39***********************比较是不是数字9

0042CF45 MOV BYTE PTR SS:[EBP-14], CL*****保存字符串K

0042CF48 JLE SHORT SuperPIM.0042CF55******小于就跳走!

0042CF4A CMP CL, 41***********************比较是不是A

0042CF4D JGE SHORT SuperPIM.0042CF55******大于就跳走

0042CF4F ADD CL, 0F6

0042CF52 MOV BYTE PTR SS:[EBP-14], CL

0042CF55 CMP EBX, [ARG.3]*****************跳到这里!比较0和14

0042CF58 JGE SHORT SuperPIM.0042CF67******大于等于就跳走!

0042CF5A PUSH [LOCAL.5]

0042CF5D LEA ECX, [LOCAL.4]

0042CF60 CALL SuperPIM.00417EAF

0042CF65 JMP SHORT SuperPIM.0042CFA5******无条件跳

0042CF67 MOV EAX, EBX

0042CF69 CDQ

0042CF6A IDIV [ARG.3]

0042CF6D MOVSX ECX, CL

0042CF70 PUSH 2B

0042CF72 MOV ESI, EDX

0042CF74 MOVSX EAX, BYTE PTR DS:[ESI+EDI]

0042CF78 LEA EAX, DWORD PTR DS:[EAX+ECX-60]

0042CF7C CDQ

0042CF7D POP ECX

0042CF7E IDIV ECX

0042CF80 ADD DL, 30

0042CF83 CMP DL, 39

0042CF86 MOV BYTE PTR SS:[EBP-14], DL

0042CF89 JLE SHORT SuperPIM.0042CF96

0042CF8B CMP DL, 41

0042CF8E JGE SHORT SuperPIM.0042CF96

0042CF90 ADD DL, 0F6

0042CF93 MOV BYTE PTR SS:[EBP-14], DL

0042CF96 PUSH [LOCAL.5]

0042CF99 LEA ECX, [LOCAL.4]

0042CF9C PUSH ESI

0042CF9D CALL SuperPIM.0042CE4B

0042CFA2 MOV ESI, [LOCAL.8]

0042CFA5 MOV EDI, [LOCAL.4]***************上面跳到这里!

0042CFA8 INC EBX*************************EBX加1

0042CFA9 CMP EBX, [LOCAL.6]**************比较1和14

0042CFAC JL SuperPIM.0042CF1B***********小于就跳,循环,序列号只有15位,这里循环20次,取完后又从第一位取出!

************************************循环结束后得到的值是:K3L2LPBDW1F2H4B8S0UY这就是我们要的注册码!

0042CFB2 MOV ECX, [ARG.1]

0042CFB5 LEA EAX, [LOCAL.4]

0042CFB8 PUSH EAX

0042CFB9 CALL SuperPIM.00401F36

0042CFBE LEA ECX, DWORD PTR DS:[EDI-10]

0042CFC1 CALL SuperPIM.00401B5D

0042CFC6 MOV ECX, [LOCAL.3]

0042CFC9 MOV EAX, [ARG.1]

0042CFCC POP EDI

0042CFCD POP ESI

0042CFCE POP EBX

0042CFCF MOV DWORD PTR FS:[0], ECX

0042CFD6 LEAVE

0042CFD7 RETN

写这篇文章的目的是想说明如何在OD下万能断点,算法过程太烦琐,就不想多跟了!希望能对大家有点启发,如果觉得这篇破文还行帮我顶一下吧!也是希望有更多的人能看到,对更多的人有益,我也是一只菜鸟,请大家多多指点!

我的序列号:GK342QZ0C6RE03L

注册码:K3L2LPBDW1F2H4B8S0UY


第三篇 OllyDbg之中断方法篇

第二卷 OllyDBG 技巧系列 第三篇 OllyDbg之中断方法篇

作者：fxyang

Ollydbg是一个新的32位的汇编层调试软件。适应于windows98、me、2000、xp和2003操作系统。由于他具有图形窗口界面，所以操作方便、直观，是cracker的好工具。

由于Ollydbg没有了TRW2000的万能断点，所以许多的新手感觉到用Ollydbg断点不好找。现在我来的说说Ollydbg下中断的几种方法。本人是个菜鸟，水平有限，可能不能完整的写出来，也可能存在错误。请大家指正。

我所表述的是Ollydbg v1.09d中文版，其他版本和英文版下自己参考。

第一 寻常断点

Ollydbg中一般下中断的方法，就是在程序的地址处用鼠标选择这一行。然后按F2键，这时被选择的那一行的地址会变成别的颜色，就表示这个地址处下了中断。然后运行程序时只有到这个地址处就会被Ollydbg中断。

这个方法用的比较多，所以把他称作寻常断点。

如果有命令行插件，就可以在命令窗口中输入BPX xxxxxxxx 下断点。

优点：只要自己怀疑是重要的代码处都可以下这种下断点，不受条件的限制，所以方便实用。

缺点：如果不知道代码功能下断点具有盲目性。

第二 API断点

Ollydbg中一般下API中断的方法，有二种。

1. 在代码窗口中点鼠标右键，出现功能菜单。在[搜索]选择项下有〔当前模块的名称〕和〔全部模块的名称〕俩项，选择其中的一项就打开了程序调用API的窗口，在这个窗口中选择你要跟踪的API函数名。双击这个函数就能到程序的调用地址处。然后用F2下中断。也可以在API窗口中选择需要跟踪的函数点鼠标右键出现功能菜单，选择〔在每个参考设置断点〕。同样下了断点。

快捷方式：Ctrl+N

2. 在命令行窗口中输入BPX API函数名或者BP API函数名 后回车。这时出现了所有调用这个函数的地址的窗口，在这个窗口中可以看到调用这个API函数的地址已改变了颜色。说明下好了断点。

说明一下：BPX一般中断在程序调用API的地址处。BP会中断在API的写入地址处。二这有所不同，根据需要选择。

优点：这种方法下的断点是针对每一个API函数的，所以具有明确的目的。

缺点：关键的API函数不容易找到。所以有时下的断点没有作用。

第三 内存断点（跟踪关键数据的断点）

Ollydbg中的内存断点相当于TRW中的bpm 断点。

下断点的方法是：在程序运行中断时选择界面中的转存窗口，用光标选择内存中的一段关键数据（颜色会改变），然后右击鼠标出现功能菜单。选择〔断点〕项，其中有二个选择〔内存访问〕和〔内存写入〕。

〔内存访问〕断点是程序运行时要调用被选择的内存数据时就会被Ollydbg中断，根据这个特点在破解跟踪时只要在关键数据内存中下中断就可以知道程序在什么地方和什么时候用到了跟踪的数据。对于一些复杂算法和流程变态的算法跟踪有很大的帮助。从破解上讲，一个注册码的生成一定是由一些关键数据或者原始数据计算来的。所以在内存中一定要用到这些关键数据。那么〔内存访问〕断点就是最好的中断方法。

〔内存写入〕断点是程序运行时向被选择的内存地址写入数据时就会被Ollydbg中断。根据这个特点在破解时可以跟踪一个关键数据是什么时候生成的，生成的代码段在那个地方。所以一个关键的数据如果不知道他的由来就可以用〔内存访问〕断点查找计算的核心。

内存中断的下断点还有另外的一种方法：程序运行时如果知道关键的数据，比如我们输入的试验码、程序生成的序列号等。这时在内存中一定存在这些数据。用Alt+M打开内存窗口，在这个窗口中搜索知道的关键数据。用光标选择这些数据同样下内存中断，这种方法更容易找的关键的数据。

优点：断点是直接面向关键数据的，所以比较容易到核心部分。

缺点：内存断点重新运行后会消失，干扰比较多。

第四 硬件断点（跟踪关键标志的断点）

硬件断点是Olldbg所特有的断点，他不会因为重新运行就销毁，只要不删除。跟踪这个程序时就有效。但他在98系统下会不起作用。

硬件断点是根据关键标志回逆到关键代码的好方法。下中断的方法和内存断点的方法相同，有三个方式〔硬件访问〕、〔硬件写入〕、〔硬件执行〕。一般用前2个。他也同样有内存断点的特性，所以可以用内存断点的地方也可以用硬件断点。这里介绍利用他来跟踪注册标志的使用方法，一般软件的注册都用到了标志比较。即在内存地址中有一个标志，在判断是不是注册时比较标志的值。不同的值表示不同的注册状态。这个标志的地址一般比较固定。根据这个特点可以下硬件断点来跟踪标志位是什么地方被标志的。

方法：在转存窗口中选择到标志存放的内存地址处，然后选择标志值。下〔硬件写入〕中断（根据标志的字节下不同的长度）。重新运行程序你会发现Ollydbg会不断的中断在这个标志的内存地址处。在功能菜单的〔调试〕选项下选择〔硬件断点〕就打开了硬件断点的窗口，在这个窗口中选择〔跟踪〕，这时转存窗口就会来到被下中断的内存地址处。运行程序跟踪内存地址中的值就会知道被赋标志的代码，跟踪到计算的核心。〔硬件访问〕的使用可以知道程序在运行时多少地方用到了这个注册标志。对于破解复杂效验的程序十分的有效。

以上只是我的总结，本想每个方法用一段例子，无奈时间有限，请大家原谅了。

当然，还是许多的下中断的方法，本人水平有限，不能一一尽述。只当抛砖引玉，请大侠们赐教。

Fxyang

属于中国破解组织[OCN][BCG][FCG]


第四篇 OllyDbg小技巧

第二卷 OllyDBG 技巧系列 第四篇 OllyDbg小技巧

作者：kongfoo

1)ctrl+(小键盘)*

改EIP到选定行。程序把代码搬到动态申请的内存中执行，要把这些代码移回程序空间，这时就可以用ctrl+*把EIP改去移动代码。有时候写代码时会有错，不要紧，不用restart，改EIP回去重新执行。

2)binary copy/paste

代码写好了，但下次要用，又重写？用binary copy把代码放到notepad，下次用时再binary paste，一劳永逸。

3)Log

就是工具栏第一个按钮。壳运行后点一点这个按钮看看？最后一次异常的地址一目了然。


第五篇 OllyDbg暂停技巧

第二卷 OllyDBG 技巧系列 第五篇 OllyDbg暂停技巧

作者：liyangsj

出现错误对话框的杀手锏－－暂停--初学者适用

……………………………………………………………………………………………………………………………………………………

理论：

出现一个对话框必然是程序调用一个子程序来完成，在汇编状态下也就是必须要调用一个“CALL”来完成程序的调用，当程序出现错误对话框时，此时用OD的暂停键，会暂停在调用这个子程序代码里面，但在汇编代码里面这个完成绘制对话框的CALL里面也会调用很多的CALL，而且会一层一层地调用，最终还会调用系统，这一点可以暂停后我们会在堆栈中发现。那怎么才能一步步从最后面的一层一层的CALL慢慢返回到我们要找的最原始的调用子程序的CALL呢？这个是关键，如果找到了，这个CALL所在的段也就是最关键的了，能跳过它的跳转也是最关键的跳转了。这一点并不难。

我们再来说一下CALL的调用，调用一个子程序的时候：1、向堆栈中压入下一行程序的地址；2.JMP到call的子程序地址处。关键是1、在堆栈中压入了下一行程序的地址，其目的是为了调用子程序结束后返回用的，如果走到一个CALL的时候我们F7一次跟进后会停在所调用的子程序的开头第一个代码处，这时看堆栈的第一行压入了调用时的下一行地址。这个也是我们要利用的：我们只要在程序某段的开头第一行代码下断（下面也行，这样可以避免不必要的干扰）中断后在堆栈中的第一行“选反汇编中跟踪”就可以返回到上一层的调用这里的地方了，回到上一层后，如果发现关键段不是这段，再在此段的开头第一行下断，向上找。这样可以一层一层向上找，直到找到最初的调用的CALL，这里就是关键段了！当然暂停后用AIT+K也可以看的到（但觉得有时不能准确定位）。

……………………………………………………………………………………………………………………………………………………

实践一下：我的用的是photoshop7.0 8.0不上手，但发现市面所能找到的都有时间限制（我这里）只能在2003之前用，所以我的机器一段时间还处在2年前火了，改之。

将时间改在2004运行程序，当程序“初始化……”时，出现错误对话框“不能完成请求，因为该序列号已过时”；此时暂停。

暂停后在堆栈中找能返回主程序：

0012E7C8 |00BC0067 返回到 Photosho.00BC0067 来自 USER32.DialogBoxParamW

返回到这里

00BC0061 |. FF>call dword ptr ds:[<&USER32.Dialog>; /DialogBoxParamW

00BC0067 |. 89>mov dword ptr ss:[ebp-24],eax

在这一段的开头部分下断：(并先改时间到2003年看看2003年能不能断下，如果能断下，证明时间限制的分叉点还在上面还要继续向上一层找关键哦！）

……………………………………………………………………………………

00BBFF20 /$ 55 push ebp

00BBFF21 |. 8B>mov ebp,esp

00BBFF23 |. 6A>push -1

00BBFF25 |. 68>push Photosho.00FC6F38

00BBFF2A |. 68>push Photosho.00C3C678 ; SE 句柄安装

00BBFF2F |. 64>mov eax,dword ptr fs:[0]

00BBFF35 |. 50 push eax

00BBFF36 |. 64>mov dword ptr fs:[0],esp

00BBFF3D |. 83>sub esp,54

00BBFF40 |. 53 push ebx

00BBFF41 |. 56 push esi

00BBFF42 |. 57 push edi

00BBFF43 |. C7>mov dword ptr ss:[ebp-24],-1

00BBFF4A |. C7>mov dword ptr ss:[ebp-20],0

00BBFF51 |. A1>mov eax,dword ptr ds:[1203FA0]

00BBFF56 |. 85>test eax,eax

00BBFF58 |. 0F>jnz Photosho.00BC0027

00BBFF5E |. 33>xor edi,edi

00BBFF60 |. A1>mov eax,dword ptr ds:[1203F98]

00BBFF65 |. 85>test eax,eax

00BBFF67 |. 74>je short Photosho.00BBFF74

00BBFF69 |. 68>push Photosho.00F72A18 ; /String "IsolationAware function called after IsolationAwareCleanup

"

00BBFF6E |. FF>call dword ptr ds:[<&KERNEL32.Outp>; /OutputDebugStringA

00BBFF74 |> A1>mov eax,dword ptr ds:[1203FA0]

00BBFF79 |. 85>test eax,eax

00BBFF7B |. 75>jnz short Photosho.00BBFFFB

00BBFF7D |. A1>mov eax,dword ptr ds:[1203F98]

00BBFF82 |. 85>test eax,eax

00BBFF84 |. 75>jnz short Photosho.00BBFF8F

00BBFF86 |. E8>call Photosho.004BEEB0

00BBFF8B |. 85>test eax,eax

00BBFF8D |. 74>je short Photosho.00BC0000

00BBFF8F |> 8B>mov ebx,dword ptr ds:[11648A4]

00BBFF95 |. A1>mov eax,dword ptr ds:[128EE48]

00BBFF9A |. 85>test eax,eax

00BBFF9C |. 75>jnz short Photosho.00BBFFF0

00BBFF9E |. A1>mov eax,dword ptr ds:[128EE4C]

00BBFFA3 |. 85>test eax,eax

00BBFFA5 |. 75>jnz short Photosho.00BBFFDB

00BBFFA7 |. 68>push Photosho.01176B58 ; /pModule "Kernel32.dll"

00BBFFAC |. FF>call dword ptr ds:[<&KERNEL32.GetM>; /GetModuleHandleW

00BBFFB2 |. 8B>mov esi,eax

00BBFFB4 |. 85>test esi,esi

00BBFFB6 |. 75>jnz short Photosho.00BBFFD0

00BBFFB8 |. FF>call dword ptr ds:[<&KERNEL32.GetL>; [GetLastError

00BBFFBE |. 83>cmp eax,78

00BBFFC1 |. 75>jnz short Photosho.00BBFFD0

00BBFFC3 |. 68>push Photosho.01176B48 ; /pModule "Kernel32.dll"

00BBFFC8 |. FF>call dword ptr ds:[<&KERNEL32.GetM>; /GetModuleHandleA

00BBFFCE |. 8B>mov esi,eax

00BBFFD0 |> 8B>mov eax,esi

00BBFFD2 |. A3>mov dword ptr ds:[128EE4C],eax

00BBFFD7 |. 85>test esi,esi

00BBFFD9 |. 74>je short Photosho.00BBFFE7

00BBFFDB |> 68>push Photosho.01176B38 ; /ProcNameOrOrdinal "ActivateActCtx"

00BBFFE0 |. 50 push eax ; |hModule

00BBFFE1 |. FF>call dword ptr ds:[<&KERNEL32.GetP>; /GetProcAddress

00BBFFE7 |> A3>mov dword ptr ds:[128EE48],eax

00BBFFEC |. 85>test eax,eax

00BBFFEE |. 74>je short Photosho.00BBFFF7

00BBFFF0 |> 8D>lea ecx,dword ptr ss:[ebp-20]

00BBFFF3 |. 51 push ecx

00BBFFF4 |. 53 push ebx

00BBFFF5 |. FF>call eax

00BBFFF7 |> 85>test eax,eax

00BBFFF9 |. 74>je short Photosho.00BC0000

00BBFFFB |> BF>mov edi,1

00BC0000 |> 85>test edi,edi

00BC0002 |. 75>jnz short Photosho.00BC0027

00BC0004 |. FF>call dword ptr ds:[<&KERNEL32.GetL>; [GetLastError

00BC000A |. 83>cmp eax,7F

00BC000D |. 74>je short Photosho.00BC0014

00BC000F |. 83>cmp eax,78

00BC0012 |. 75>jnz short Photosho.00BC001F

00BC0014 |> BF>mov edi,1

00BC0019 |. 89>mov dword ptr ds:[1203FA0],edi

00BC001F |> 85>test edi,edi

00BC0021 |. 75>jnz short Photosho.00BC0027

00BC0023 |. 33>xor eax,eax

00BC0025 |. EB>jmp short Photosho.00BC002C

00BC0027 |> B8>mov eax,1

00BC002C |> 85>test eax,eax

00BC002E |. 75>jnz short Photosho.00BC0046

00BC0030 |. 83>or eax,FFFFFFFF

00BC0033 |. 8B>mov ecx,dword ptr ss:[ebp-10]

00BC0036 |. 64>mov dword ptr fs:[0],ecx

00BC003D |. 5F pop edi

00BC003E |. 5E pop esi

00BC003F |. 5B pop ebx

00BC0040 |. 8B>mov esp,ebp

00BC0042 |. 5D pop ebp

00BC0043 |. C2>retn 14

00BC0046 |> C7>mov dword ptr ss:[ebp-4],0

00BC004D |. 8B>mov edx,dword ptr ss:[ebp+18]

00BC0050 |. 52 push edx ; /lParam

00BC0051 |. 8B>mov eax,dword ptr ss:[ebp+14] ; |

00BC0054 |. 50 push eax ; |DlgProc

00BC0055 |. 8B>mov ecx,dword ptr ss:[ebp+10] ; |

00BC0058 |. 51 push ecx ; |hOwner

00BC0059 |. 8B>mov edx,dword ptr ss:[ebp+C] ; |

00BC005C |. 52 push edx ; |pTemplate

00BC005D |. 8B>mov eax,dword ptr ss:[ebp+8] ; |

00BC0060 |. 50 push eax ; |hInst

00BC0061 |. FF>call dword ptr ds:[<&USER32.Dialog>; /DialogBoxParamW

00BC0067 |. 89>mov dword ptr ss:[ebp-24],eax

………………………………………………………………………………………………………………

下断：00BBFF20 /$ 55 push ebp ctrl+f2重新运行f9断在这里

………………………………………………………………………………………………………………

在堆栈中：0012E854 00BBDDA6 返回到 Photosho.00BBDDA6 来自 Photosho.00BBFF20

………………………………………………………………………………………………………………

00BBDDA1 |. E8>call Photosho.00BBFF20 ; /第2次

00BBDDA6 |. EB>jmp short Photosho.00BBDDC2

这段：

00BBDC30 /$ 55 push ebp ; 第2次

00BBDC31 |. 8B>mov ebp,esp

00BBDC33 |. 6A>push -1

00BBDC35 |. 68>push Photosho.00F49346 ; SE 句柄安装

00BBDC3A |. 64>mov eax,dword ptr fs:[0]

00BBDC40 |. 50 push eax

00BBDC41 |. 64>mov dword ptr fs:[0],esp

00BBDC48 |. 81>sub esp,288

00BBDC4E |. 53 push ebx

00BBDC4F |. 56 push esi

00BBDC50 |. 57 push edi

00BBDC51 |. 89>mov dword ptr ss:[ebp-10],esp

00BBDC54 |. 8D>lea ecx,dword ptr ss:[ebp-18]

00BBDC57 |. E8>call Photosho.00919EF0

00BBDC5C |. 33>xor ebx,ebx

00BBDC5E |. 89>mov dword ptr ss:[ebp-4],ebx

00BBDC61 |. C6>mov byte ptr ss:[ebp-28],1

00BBDC65 |. FF>call dword ptr ds:[<&USER32.GetFoc>; [GetFocus

00BBDC6B |. 89>mov dword ptr ss:[ebp-1C],eax

00BBDC6E |. 8D>lea ecx,dword ptr ss:[ebp-30]

00BBDC71 |. E8>call Photosho.00BCE790

00BBDC76 |. C6>mov byte ptr ss:[ebp-4],1

00BBDC7A |. 88>mov byte ptr ss:[ebp-24],bl

00BBDC7D |. E8>call Photosho.009DD610

00BBDC82 |. 84>test al,al

00BBDC84 |. 74>je short Photosho.00BBDCBD

00BBDC86 |. 8B>mov eax,dword ptr ss:[ebp+C]

00BBDC89 |. 66>mov si,word ptr ds:[eax+1E]

00BBDC8D |. 66>inc si

00BBDC8F |. 88>mov byte ptr ss:[ebp-4],bl

00BBDC92 |. 8D>lea ecx,dword ptr ss:[ebp-30]

00BBDC95 |. E8>call Photosho.00BCE7D0

00BBDC9A |. C7>mov dword ptr ss:[ebp-4],-1

00BBDCA1 |. 8D>lea ecx,dword ptr ss:[ebp-18]

00BBDCA4 |. E8>call Photosho.00919F70

00BBDCA9 |. 66>mov ax,si

00BBDCAC |. 8B>mov ecx,dword ptr ss:[ebp-C]

00BBDCAF |. 64>mov dword ptr fs:[0],ecx

00BBDCB6 |. 5F pop edi

00BBDCB7 |. 5E pop esi

00BBDCB8 |. 5B pop ebx

00BBDCB9 |. 8B>mov esp,ebp

00BBDCBB |. 5D pop ebp

00BBDCBC |. C3 retn

00BBDCBD |> C6>mov byte ptr ds:[1254582],1

00BBDCC4 |. C6>mov byte ptr ds:[1254583],1

00BBDCCB |. 68>push Photosho.01255E9E

00BBDCD0 |. E8>call Photosho.00B46720

00BBDCD5 |. 83>add esp,4

00BBDCD8 |. E8>call Photosho.00BBAF70

00BBDCDD |. 33>xor esi,esi

00BBDCDF |. 89>mov dword ptr ss:[ebp-34],esi

00BBDCE2 |. 8B>mov ecx,dword ptr ds:[125457C]

00BBDCE8 |. 51 push ecx ; /hWnd > 002101D6 (\'Adobe Photoshop\',class\'Photoshop\')

00BBDCE9 |. 8B>mov edi,dword ptr ds:[<&USER32.IsW>; |USER32.IsWindow

00BBDCEF |. FF>call edi ; /IsWindow

00BBDCF1 |. 85>test eax,eax

00BBDCF3 |. 74>je short Photosho.00BBDD00

00BBDCF5 |. 8B>mov edx,dword ptr ds:[125457C]

00BBDCFB |. 89>mov dword ptr ss:[ebp-34],edx

00BBDCFE |. EB>jmp short Photosho.00BBDD15

00BBDD00 |> A1>mov eax,dword ptr ds:[125555C]

00BBDD05 |. 50 push eax

00BBDD06 |. FF>call edi

00BBDD08 |. 85>test eax,eax

00BBDD0A |. 74>je short Photosho.00BBDD18

00BBDD0C |. 8B>mov ecx,dword ptr ds:[125555C]

00BBDD12 |. 89>mov dword ptr ss:[ebp-34],ecx

00BBDD15 |> 8B>mov esi,dword ptr ss:[ebp-34]

00BBDD18 |> 53 push ebx

00BBDD19 |. 56 push esi

00BBDD1A |. E8>call Photosho.00BBE1A0

00BBDD1F |. 83>add esp,8

00BBDD22 |. 88>mov byte ptr ss:[ebp-3C],al

00BBDD25 |. E8>call Photosho.00C22960

00BBDD2A |. 88>mov byte ptr ss:[ebp-38],al

00BBDD2D |. A1>mov eax,dword ptr ds:[125555C]

00BBDD32 |. 3B>cmp esi,eax

00BBDD34 |. 74>je short Photosho.00BBDD5F

00BBDD36 |. 50 push eax

00BBDD37 |. FF>call edi

00BBDD39 |. 85>test eax,eax

00BBDD3B |. 74>je short Photosho.00BBDD5F

00BBDD3D |. 8B>mov edx,dword ptr ds:[125555C]

00BBDD43 |. 52 push edx ; /hWnd > 002101D6 (\'Adobe Photoshop\',class\'Photoshop\')

00BBDD44 |. FF>call dword ptr ds:[<&USER32.IsWind>; /IsWindowEnabled

00BBDD4A |. 85>test eax,eax

00BBDD4C |. 74>je short Photosho.00BBDD5F

00BBDD4E |. 53 push ebx ; /Enable

00BBDD4F |. A1>mov eax,dword ptr ds:[125555C] ; |

00BBDD54 |. 50 push eax ; |hWnd > 002101D6 (\'Adobe Photoshop\',class\'Photoshop\')

00BBDD55 |. FF>call dword ptr ds:[<&USER32.Enable>; /EnableWindow

00BBDD5B |. C6>mov byte ptr ss:[ebp-24],1

00BBDD5F |> 8B>mov eax,dword ptr ss:[ebp+C]

00BBDD62 |. 3B>cmp eax,ebx

00BBDD64 |. 74>je short Photosho.00BBDDD7

00BBDD66 |. 66>mov cx,word ptr ss:[ebp+14]

00BBDD6A |. 66>mov word ptr ss:[ebp-50],cx

00BBDD6E |. C7>mov dword ptr ss:[ebp-4C],1

00BBDD75 |. 89>mov dword ptr ss:[ebp-48],ebx

00BBDD78 |. 89>mov dword ptr ss:[ebp-44],eax

00BBDD7B |. 8B>mov edi,dword ptr ss:[ebp+8]

00BBDD7E |. 8A>mov dl,byte ptr ds:[edi]

00BBDD80 |. 88>mov byte ptr ss:[ebp-40],dl

00BBDD83 |. A0>mov al,byte ptr ds:[12554EF]

00BBDD88 |. 84>test al,al

00BBDD8A |. 74>je short Photosho.00BBDDA8

00BBDD8C |. 8D>lea eax,dword ptr ss:[ebp-50]

00BBDD8F |. 50 push eax

00BBDD90 |. 68>push Photosho.00BBAFC0

00BBDD95 |. 56 push esi

00BBDD96 |. 68>push 7E5

00BBDD9B |. E8>call Photosho.00BE7140

00BBDDA0 |. 50 push eax ; |Arg1

00BBDDA1 |. E8>call Photosho.00BBFF20 ; /第2次

00BBDDA6 |. EB>jmp short Photosho.00BBDDC2

……………………………………………………………………………………………………………………

下断：00BBDC30 /$ 55 push ebp ctrl+f2重新运行f9断在这里

……………………………………………………………………………………………………………………

堆栈中：

0012EB10 00BBDC23 返回到 Photosho.00BBDC23 来自 Photosho.00BBDC30

……………………………………………………………………………………………………………………

00BBDC10 /$ 8B>mov eax,dword ptr ss:[esp+8] ; 第3次

00BBDC14 |. 8B>mov ecx,dword ptr ss:[esp+4]

00BBDC18 |. 6A>push 0 ; /Arg4 00000000

00BBDC1A |. 6A>push 0 ; |Arg3 00000000

00BBDC1C |. 50 push eax ; |Arg2

00BBDC1D |. 51 push ecx ; |Arg1

00BBDC1E |. E8>call Photosho.00BBDC30 ; /第3次

00BBDC23 |. 83>add esp,10

00BBDC26 |. 48 dec eax

00BBDC27 /. C3 retn

……………………………………………………………………………………………………………………

下断：00BBDC10 /$ 8B>mov eax,dword ptr ss:[esp+8] ; 第3次ctrl+f2重新运行f9断在这里

…………………………………………………………………………………………………………………………

堆栈中：

0012EB24 00B6CCB6 返回到 Photosho.00B6CCB6 来自 Photosho.00BBDC10

…………………………………………………………………………………………………………………………

00B6CC20 /$ 6A>push -1 ; 第4次

00B6CC22 |. 68>push Photosho.00F42BDE ; SE 句柄安装

00B6CC27 |. 64>mov eax,dword ptr fs:[0]

00B6CC2D |. 50 push eax

00B6CC2E |. 64>mov dword ptr fs:[0],esp

00B6CC35 |. 83>sub esp,24

00B6CC38 |. 8D>lea ecx,dword ptr ss:[esp+4]

00B6CC3C |. E8>call Photosho.00B724D0

00B6CC41 |. 68>push Photosho.00B72800 ; 入口地址

00B6CC46 |. 68>push Photosho.00B724D0 ; 入口地址

00B6CC4B |. 6A>push 5

00B6CC4D |. 8D>lea eax,dword ptr ss:[esp+14]

00B6CC51 |. 6A>push 4

00B6CC53 |. 50 push eax

00B6CC54 |. C7>mov dword ptr ss:[esp+40],0

00B6CC5C |. E8>call Photosho.00C3BB6A

00B6CC61 |. 8B>mov ecx,dword ptr ss:[esp+38]

00B6CC65 |. B8>mov eax,2

00B6CC6A |. 51 push ecx

00B6CC6B |. 89>mov dword ptr ss:[esp+30],eax

00B6CC6F |. 8D>lea ecx,dword ptr ss:[esp+8]

00B6CC73 |. 89>mov dword ptr ss:[esp+4],eax

00B6CC77 |. E8>call Photosho.00B72820

00B6CC7C |. 68>push Photosho.01253320

00B6CC81 |. 8D>lea ecx,dword ptr ss:[esp+C]

00B6CC85 |. E8>call Photosho.00B72820

00B6CC8A |. 8B>mov ecx,dword ptr ss:[esp+34]

00B6CC8E |. 66>mov dx,word ptr ss:[esp+3C]

00B6CC93 |. 8D>lea eax,dword ptr ss:[esp]

00B6CC97 |. C6>mov byte ptr ss:[esp+1C],1

00B6CC9C |. 50 push eax

00B6CC9D |. 51 push ecx

00B6CC9E |. 66>mov word ptr ss:[esp+26],0

00B6CCA5 |. 66>mov word ptr ss:[esp+28],5

00B6CCAC |. 66>mov word ptr ss:[esp+2A],dx

00B6CCB1 |. E8>call Photosho.00BBDC10 ; 第4次

00B6CCB6 |. 83>add esp,8

00B6CCB9 |. 68>push Photosho.00B72800 ; 入口地址

…………………………………………………………………………………………………………………………

下断：00B6CC20 /$ 6A>push -1 ; 第4次ctrl+f2重新运行f9断在这里

…………………………………………………………………………………………………………………………

堆栈

0012EB60 00B6D23A 返回到 Photosho.00B6D23A 来自 Photosho.00B6CC20

…………………………………………………………………………………………………………………………

00B6D220 /$ 51 push ecx ; 第5次

00B6D221 |. 8B>mov eax,dword ptr ss:[esp+C]

00B6D225 |. 8B>mov ecx,dword ptr ss:[esp+8]

00B6D229 |. 50 push eax

00B6D22A |. 8D>lea edx,dword ptr ss:[esp+7]

00B6D22E |. 51 push ecx

00B6D22F |. 52 push edx

00B6D230 |. C6>mov byte ptr ss:[esp+F],0

00B6D235 |. E8>call Photosho.00B6CC20 ; 第5次

00B6D23A |. 83>add esp,10

00B6D23D /. C3 retn

…………………………………………………………………………………………………………………………

下断00B6D220 /$ 51 push ecx ; 第5次ctrl+f2重新运行f9断在这里

…………………………………………………………………………………………………………………………

堆栈：

0012EB74 00B7F78F 返回到 Photosho.00B7F78F 来自 Photosho.00B6D220

…………………………………………………………………………………………………………………………

00B7F760 /$ 53 push ebx ; 第6次

00B7F761 |. 8B>mov ebx,dword ptr ss:[esp+C]

00B7F765 |. 56 push esi

00B7F766 |. 8B>mov esi,dword ptr ss:[esp+14]

00B7F76A |. 57 push edi

00B7F76B |. 8B>mov edi,dword ptr ss:[esp+10]

00B7F76F |. 56 push esi

00B7F770 |. 53 push ebx

00B7F771 |. 57 push edi

00B7F772 |. E8>call Photosho.00B7F540

00B7F777 |. 83>add esp,0C

00B7F77A |. 8B>mov ecx,esi

00B7F77C |. E8>call Photosho.00B6FF70

00B7F781 |. 84>test al,al

00B7F783 |. 75>jnz short Photosho.00B7F792

00B7F785 |. 66>mov ax,word ptr ds:[edi]

00B7F788 |. 50 push eax

00B7F789 |. 56 push esi

00B7F78A |. E8>call Photosho.00B6D220 ; 第6次

00B7F78F |. 83>add esp,8

00B7F792 |> C6>mov byte ptr ds:[1256262],0

堆栈中：

0012EB8C 0043339D 返回到 Photosho.0043339D 来自 Photosho.00B7F760 返回后到了最后点

………………………………………………………………………………………………………………

00433160 $ 55 push ebp ; 第7次到了

00433161 . 8B>mov ebp,esp

00433163 . 6A>push -1

00433165 . 68>push Photosho.00E5396A ; SE 句柄安装

0043316A . 64>mov eax,dword ptr fs:[0]

00433170 . 50 push eax

00433171 . 64>mov dword ptr fs:[0],esp

00433178 . 81>sub esp,54C

0043317E . 53 push ebx

0043317F . 56 push esi

00433180 . 57 push edi

00433181 . 89>mov dword ptr ss:[ebp-10],esp

00433184 . 33>xor ebx,ebx

00433186 . 88>mov byte ptr ss:[ebp-114],bl

0043318C . 88>mov byte ptr ss:[ebp-113],bl

00433192 . 8D>lea eax,dword ptr ss:[ebp-114]

00433198 . 50 push eax

00433199 . 68>push Photosho.012561E8

0043319E . E8>call Photosho.00417E80

004331A3 . 83>add esp,8

004331A6 . 84>test al,al

004331A8 . 0F>jnz Photosho.004332DC

004331AE . E8>call Photosho.00B74B90

004331B3 . 8D>lea ecx,dword ptr ss:[ebp-22C]

004331B9 . E8>call Photosho.00B724D0

004331BE . 89>mov dword ptr ss:[ebp-4],ebx

004331C1 . E8>call Photosho.00B6E090

004331C6 . 50 push eax

004331C7 . 6A>push -1

004331C9 . 68>push Photosho.011648EC ; ASCII "$$$/MacApp/MissingComponentsAdobe Photoshop can not run on this computer because ^0"

004331CE . 8D>lea ecx,dword ptr ss:[ebp-234]

004331D4 . E8>call Photosho.00B72530

004331D9 . C6>mov byte ptr ss:[ebp-4],1

004331DD . 8D>lea ecx,dword ptr ss:[ebp-234]

004331E3 . 51 push ecx

004331E4 . 8D>lea ecx,dword ptr ss:[ebp-22C]

004331EA . E8>call Photosho.00B72820

004331EF . 88>mov byte ptr ss:[ebp-4],bl

004331F2 . 8D>lea ecx,dword ptr ss:[ebp-234]

004331F8 . E8>call Photosho.00B72800

004331FD . 8D>lea edx,dword ptr ss:[ebp-114]

00433203 . 52 push edx

00433204 . 8D>lea eax,dword ptr ss:[ebp-238]

0043320A . 50 push eax

0043320B . E8>call Photosho.004335D0

00433210 . 83>add esp,8

00433213 . C6>mov byte ptr ss:[ebp-4],2

00433217 . 6A>push 1

00433219 . 50 push eax

0043321A . 53 push ebx

0043321B . 8D>lea ecx,dword ptr ss:[ebp-22C]

00433221 . E8>call Photosho.00B737B0

00433226 . 88>mov byte ptr ss:[ebp-4],bl

00433229 . 8D>lea ecx,dword ptr ss:[ebp-238]

0043322F . E8>call Photosho.00B72800

00433234 . 88>mov byte ptr ss:[ebp-228],bl

0043323A . 88>mov byte ptr ss:[ebp-227],bl

00433240 . 6A>push 1 ; /Arg3 00000001

00433242 . 68>push 0FF ; |Arg2 000000FF

00433247 . 8D>lea ecx,dword ptr ss:[ebp-228] ; |

0043324D . 51 push ecx ; |Arg1

0043324E . 8D>lea ecx,dword ptr ss:[ebp-22C] ; |

00433254 . E8>call Photosho.00B72F30 ; /Photosho.00B72F30

00433259 . 68>push Photosho.011648E8

0043325E . 8D>lea ecx,dword ptr ss:[ebp-228]

00433264 . E8>call Photosho.00B784E0

00433269 . 68>push Photosho.01203F7C

0043326E . 8D>lea ecx,dword ptr ss:[ebp-440]

00433274 . E8>call Photosho.00B781C0

00433279 . 8B>mov esi,eax

0043327B . 68>push Photosho.01203F7C

00433280 . 8D>lea ecx,dword ptr ss:[ebp-544]

00433286 . E8>call Photosho.00B781C0

0043328B . 8B>mov ebx,eax

0043328D . 68>push Photosho.01203F7C

00433292 . 8D>lea ecx,dword ptr ss:[ebp-33C]

00433298 . E8>call Photosho.00B781C0

0043329D . 50 push eax

0043329E . 56 push esi

0043329F . 53 push ebx

004332A0 . 8D>lea edx,dword ptr ss:[ebp-228]

004332A6 . 52 push edx

004332A7 . E8>call Photosho.00BBAD70

004332AC . 68>push 89

004332B1 . E8>call Photosho.00BBE060

004332B6 . 83>add esp,14

004332B9 . C7>mov dword ptr ss:[ebp-4],-1

004332C0 . 8D>lea ecx,dword ptr ss:[ebp-22C]

004332C6 . E8>call Photosho.00B72800

004332CB . 8B>mov ecx,dword ptr ss:[ebp-C]

004332CE . 64>mov dword ptr fs:[0],ecx

004332D5 . 5F pop edi

004332D6 . 5E pop esi

004332D7 . 5B pop ebx

004332D8 . 8B>mov esp,ebp

004332DA . 5D pop ebp

004332DB . C3 retn

004332DC > 6A>push 0A

004332DE . E8>call Photosho.00C161A0

004332E3 . E8>call Photosho.0040FE90

004332E8 . E8>call Photosho.00C20380

004332ED . E8>call Photosho.0040FE90

004332F2 . E8>call Photosho.0040FE90

004332F7 . E8>call Photosho.00BA0340

004332FC . E8>call Photosho.0040FE90

00433301 . 68>push 0A4

00433306 . E8>call Photosho.00B97E30

0043330B . 83>add esp,8

0043330E . 89>mov dword ptr ss:[ebp-54C],eax

00433314 . C7>mov dword ptr ss:[ebp-4],3

0043331B . 3B>cmp eax,ebx

0043331D . 74>je short Photosho.0043332A

0043331F . 8B>mov ecx,eax

00433321 . E8>call Photosho.008449B0

00433326 . 8B>mov esi,eax

00433328 . EB>jmp short Photosho.0043332C

0043332A > 33>xor esi,esi

0043332C > 89>mov dword ptr ss:[ebp-548],esi

00433332 . C7>mov dword ptr ss:[ebp-4],-1

00433339 . 88>mov byte ptr ss:[ebp-124],bl

0043333F . 8B>mov ecx,esi

00433341 . E8>call Photosho.00844A90

00433346 . 68>push Photosho.00433430

0043334B . E8>call Photosho.00C3BFE8

00433350 . 68>push Photosho.00433440

00433355 . E8>call Photosho.00C3BFD8

0043335A . 83>add esp,8

0043335D . 8B>mov edi,eax

0043335F . 89>mov dword ptr ss:[ebp-120],edi

00433365 . C7>mov dword ptr ss:[ebp-4],4

0043336C . C6>mov byte ptr ss:[ebp-4],5

00433370 . 8B>mov ecx,esi

00433372 . E8>call Photosho.00846920

00433377 . EB>jmp short Photosho.004333E1

00433379 . C6>mov byte ptr ss:[ebp-124],1

00433380 . 8B>mov ecx,dword ptr ss:[ebp-230]

00433386 . 8D>lea eax,dword ptr ds:[ecx+10]

00433389 . 66>cmp word ptr ds:[eax],0

0043338D . 74>je short Photosho.004333A6

0043338F . 8D>lea edx,dword ptr ds:[ecx+18]

00433392 . 52 push edx

00433393 . 83>add ecx,14

00433396 . 51 push ecx

00433397 . 50 push eax

00433398 . E8>call Photosho.00B7F760 ; 第7次

0043339D . 83>add esp,0C

…………………………………………………………………………………………………………………………

下断：00433160 $ 55 push ebp ; 第7次到了

…………………………………………………………………………………………………………………………

怎么知道转折点就在这段里面？

把时间调到2003年时候发现也断下来了！

慢慢跟吧，并且把时间一次改成2004年一次改成2003年观察到底哪里不同！

00433160 $ 55 push ebp ; 第7次到了

00433161 . 8B>mov ebp,esp

00433163 . 6A>push -1

00433165 . 68>push Photosho.00E5396A ; SE 句柄安装

0043316A . 64>mov eax,dword ptr fs:[0]

00433170 . 50 push eax

00433171 . 64>mov dword ptr fs:[0],esp

00433178 . 81>sub esp,54C

0043317E . 53 push ebx

0043317F . 56 push esi

00433180 . 57 push edi

00433181 . 89>mov dword ptr ss:[ebp-10],esp

00433184 . 33>xor ebx,ebx

00433186 . 88>mov byte ptr ss:[ebp-114],bl

0043318C . 88>mov byte ptr ss:[ebp-113],bl

00433192 . 8D>lea eax,dword ptr ss:[ebp-114]

00433198 . 50 push eax

00433199 . 68>push Photosho.012561E8

0043319E . E8>call Photosho.00417E80

004331A3 . 83>add esp,8

004331A6 . 84>test al,al

004331A8 . 0F>jnz Photosho.004332DC

004331AE . E8>call Photosho.00B74B90

004331B3 . 8D>lea ecx,dword ptr ss:[ebp-22C]

004331B9 . E8>call Photosho.00B724D0

004331BE . 89>mov dword ptr ss:[ebp-4],ebx

004331C1 . E8>call Photosho.00B6E090

004331C6 . 50 push eax

004331C7 . 6A>push -1

004331C9 . 68>push Photosho.011648EC ; ASCII "$$$/MacApp/MissingComponentsAdobe Photoshop can not run on this computer because ^0"

004331CE . 8D>lea ecx,dword ptr ss:[ebp-234]

004331D4 . E8>call Photosho.00B72530

004331D9 . C6>mov byte ptr ss:[ebp-4],1

004331DD . 8D>lea ecx,dword ptr ss:[ebp-234]

004331E3 . 51 push ecx

004331E4 . 8D>lea ecx,dword ptr ss:[ebp-22C]

004331EA . E8>call Photosho.00B72820

004331EF . 88>mov byte ptr ss:[ebp-4],bl

004331F2 . 8D>lea ecx,dword ptr ss:[ebp-234]

004331F8 . E8>call Photosho.00B72800

004331FD . 8D>lea edx,dword ptr ss:[ebp-114]

00433203 . 52 push edx

00433204 . 8D>lea eax,dword ptr ss:[ebp-238]

0043320A . 50 push eax

0043320B . E8>call Photosho.004335D0

00433210 . 83>add esp,8

00433213 . C6>mov byte ptr ss:[ebp-4],2

00433217 . 6A>push 1

00433219 . 50 push eax

0043321A . 53 push ebx

0043321B . 8D>lea ecx,dword ptr ss:[ebp-22C]

00433221 . E8>call Photosho.00B737B0

00433226 . 88>mov byte ptr ss:[ebp-4],bl

00433229 . 8D>lea ecx,dword ptr ss:[ebp-238]

0043322F . E8>call Photosho.00B72800

00433234 . 88>mov byte ptr ss:[ebp-228],bl

0043323A . 88>mov byte ptr ss:[ebp-227],bl

00433240 . 6A>push 1 ; /Arg3 00000001

00433242 . 68>push 0FF ; |Arg2 000000FF

00433247 . 8D>lea ecx,dword ptr ss:[ebp-228] ; |

0043324D . 51 push ecx ; |Arg1

0043324E . 8D>lea ecx,dword ptr ss:[ebp-22C] ; |

00433254 . E8>call Photosho.00B72F30 ; /Photosho.00B72F30

00433259 . 68>push Photosho.011648E8

0043325E . 8D>lea ecx,dword ptr ss:[ebp-228]

00433264 . E8>call Photosho.00B784E0

00433269 . 68>push Photosho.01203F7C

0043326E . 8D>lea ecx,dword ptr ss:[ebp-440]

00433274 . E8>call Photosho.00B781C0

00433279 . 8B>mov esi,eax

0043327B . 68>push Photosho.01203F7C

00433280 . 8D>lea ecx,dword ptr ss:[ebp-544]

00433286 . E8>call Photosho.00B781C0

0043328B . 8B>mov ebx,eax

0043328D . 68>push Photosho.01203F7C

00433292 . 8D>lea ecx,dword ptr ss:[ebp-33C]

00433298 . E8>call Photosho.00B781C0

0043329D . 50 push eax

0043329E . 56 push esi

0043329F . 53 push ebx

004332A0 . 8D>lea edx,dword ptr ss:[ebp-228]

004332A6 . 52 push edx

004332A7 . E8>call Photosho.00BBAD70

004332AC . 68>push 89

004332B1 . E8>call Photosho.00BBE060

004332B6 . 83>add esp,14

004332B9 . C7>mov dword ptr ss:[ebp-4],-1

004332C0 . 8D>lea ecx,dword ptr ss:[ebp-22C]

004332C6 . E8>call Photosho.00B72800

004332CB . 8B>mov ecx,dword ptr ss:[ebp-C]

004332CE . 64>mov dword ptr fs:[0],ecx

004332D5 . 5F pop edi

004332D6 . 5E pop esi

004332D7 . 5B pop ebx

004332D8 . 8B>mov esp,ebp

004332DA . 5D pop ebp

004332DB . C3 retn

004332DC > 6A>push 0A

004332DE . E8>call Photosho.00C161A0

004332E3 . E8>call Photosho.0040FE90

004332E8 . E8>call Photosho.00C20380

004332ED . E8>call Photosho.0040FE90

004332F2 . E8>call Photosho.0040FE90

004332F7 . E8>call Photosho.00BA0340

004332FC . E8>call Photosho.0040FE90

00433301 . 68>push 0A4

00433306 . E8>call Photosho.00B97E30

0043330B . 83>add esp,8

0043330E . 89>mov dword ptr ss:[ebp-54C],eax

00433314 . C7>mov dword ptr ss:[ebp-4],3

0043331B . 3B>cmp eax,ebx

0043331D . 74>je short Photosho.0043332A

0043331F . 8B>mov ecx,eax

00433321 . E8>call Photosho.008449B0

00433326 . 8B>mov esi,eax

00433328 . EB>jmp short Photosho.0043332C

0043332A > 33>xor esi,esi

0043332C > 89>mov dword ptr ss:[ebp-548],esi

00433332 . C7>mov dword ptr ss:[ebp-4],-1

00433339 . 88>mov byte ptr ss:[ebp-124],bl

0043333F . 8B>mov ecx,esi

00433341 . E8>call Photosho.00844A90

00433346 . 68>push Photosho.00433430

0043334B . E8>call Photosho.00C3BFE8

00433350 . 68>push Photosho.00433440

00433355 . E8>call Photosho.00C3BFD8

0043335A . 83>add esp,8

0043335D . 8B>mov edi,eax

0043335F . 89>mov dword ptr ss:[ebp-120],edi

00433365 . C7>mov dword ptr ss:[ebp-4],4

0043336C . C6>mov byte ptr ss:[ebp-4],5

00433370 . 8B>mov ecx,esi

00433372 . E8>call Photosho.00846920 ; 关键所在。

00433377 . EB>jmp short Photosho.004333E1

00433379 . C6>mov byte ptr ss:[ebp-124],1

00433380 . 8B>mov ecx,dword ptr ss:[ebp-230]

00433386 . 8D>lea eax,dword ptr ds:[ecx+10]

00433389 . 66>cmp word ptr ds:[eax],0

0043338D . 74>je short Photosho.004333A6

0043338F . 8D>lea edx,dword ptr ds:[ecx+18]

00433392 . 52 push edx

00433393 . 83>add ecx,14

00433396 . 51 push ecx

00433397 . 50 push eax

00433398 . E8>call Photosho.00B7F760 ; 所以这里就是真正调用“序列号过时”的CALL，所以最直接的不让程序运行到这里

0043339D . 83>add esp,0C

004333A0 . B8>mov eax,Photosho.004333D3

004333A5 . C3 retn

修改时间对比发现如果时间在2003之前，经过00433372 . E8>call Photosho.00846920会最直接地到了下一句00433377 . EB>jmp short Photosho.004333E1

会跳过如果时间超过2003年走过00433372 . E8>call Photosho.00846920出出现异常会到00433379 . C6>mov byte ptr ss:[ebp-124],1

问题就在00433372 . E8>call Photosho.00846920

…………………………………………………………………………………………………………………………

进入00433372 . E8>call Photosho.00846920 ; 问题就在这里

…………………………………………………………………………………………………………………………

00846920 /$ 55 push ebp

00846921 |. 8B>mov ebp,esp

00846923 |. 6A>push -1

00846925 |. 68>push Photosho.00EE1E7B ; SE 句柄安装

0084692A |. 64>mov eax,dword ptr fs:[0]

00846930 |. 50 push eax

00846931 |. 64>mov dword ptr fs:[0],esp

00846938 |. 83>sub esp,24

0084693B |. 53 push ebx

0084693C |. 56 push esi

0084693D |. 57 push edi

0084693E |. 89>mov dword ptr ss:[ebp-10],esp

00846941 |. 8B>mov esi,ecx

00846943 |. E8>call Photosho.00876050

00846948 |. 33>xor ebx,ebx

0084694A |. 89>mov dword ptr ss:[ebp-4],ebx

0084694D |. E8>call Photosho.0077D2E0 ; 这里会异常跟进

00846952 |. 6A>push 4

00846954 |. E8>call Photosho.00B97E30

……………………………………………………………………………………………………………………

进入0084694D |. E8>call Photosho.0077D2E0 ; 这里会异常跟进

…………………………………………………………………………………………………………………………

0077D3C2 . F2>repne scas byte ptr es:[edi]

0077D3C4 . F7>not ecx

0077D3C6 . 49 dec ecx

0077D3C7 . 88>mov byte ptr ds:[1248220],cl

0077D3CD . 68>push 0FF

0077D3D2 . 8D>lea eax,dword ptr ss:[ebp-238]

0077D3D8 . 50 push eax

0077D3D9 . 68>push Photosho.01248221 ; ASCII "C:/Program Files/Adobe/Photoshop 7.0/"

0077D3DE . E8>call Photosho.00C3C010

0077D3E3 . 83>add esp,0C

0077D3E6 . C6>mov byte ptr ds:[124831F],0

0077D3ED . 8B>mov ecx,dword ptr ds:[1235E50] ; Photosho.01235E58

0077D3F3 . E8>call Photosho.0077D250 ; 到这里还是会异常

0077D3F8 . 8B>mov ecx,dword ptr ds:[1249550] ; Photosho.01249558

0077D3FE . E8>call Photosho.0077D250

0077D403 . E8>call Photosho.00B468F0

0077D408 . 6A>push 1

0077D40A . 8D>lea ecx,dword ptr ss:[ebp-12C]

0077D410 . E8>call Photosho.009DD670

0077D415 . C7>mov dword ptr ss:[ebp-4],1

0077D41C . 6A>push 1

0077D41E . 8D>lea ecx,dword ptr ss:[ebp-120]

………………………………………………………………………………………………………………

进入0077D3F3 . E8>call Photosho.0077D250 ; 到这里还是会异常

……………………………………………………………………………………………………………………

0077D250 /$ 56 push esi

0077D251 |. 8B>mov esi,ecx

0077D253 |. 8B>mov eax,dword ptr ds:[esi]

0077D255 |. FF>call dword ptr ds:[eax+20] ; 还是有异常跟进

0077D258 |. C6>mov byte ptr ds:[esi+2C],1

0077D25C |. 66>mov cx,word ptr ds:[1256260]

0077D263 |. 51 push ecx

0077D264 |. E8>call Photosho.00B95180

0077D269 |. 83>add esp,4

0077D26C |. 5E pop esi

0077D26D /. C3 retn

……………………………………………………………………………………………………………………

进入0077D255 |. FF>call dword ptr ds:[eax+20] ; 还是有异常跟进

并把时间调到2003与2004两次对比发现：

……………………………………………………………………………………………………………………

005D67B0 . 6A>push -1

005D67B2 . 68>push Photosho.00E90A18 ; SE 句柄安装

005D67B7 . 64>mov eax,dword ptr fs:[0]

005D67BD . 50 push eax

005D67BE . 64>mov dword ptr fs:[0],esp

005D67C5 . 51 push ecx

005D67C6 . 56 push esi

005D67C7 . 8B>mov esi,ecx

005D67C9 . 8B>mov eax,dword ptr ds:[esi]

005D67CB . FF>call dword ptr ds:[eax+2C]

005D67CE . 8A>mov al,byte ptr ds:[esi+51]

005D67D1 . 84>test al,al

005D67D3 . 74>je short Photosho.005D67E0 ; 跳

005D67D5 . 8A>mov al,byte ptr ds:[esi+4B]

005D67D8 . 84>test al,al

005D67DA . 75>jnz short Photosho.005D67E0

005D67DC . B0>mov al,1

005D67DE . EB>jmp short Photosho.005D67E2

005D67E0 > 32>xor al,al

005D67E2 > 8A>mov cl,byte ptr ds:[esi+4F]

005D67E5 . 53 push ebx

005D67E6 . 84>test cl,cl

005D67E8 . 75>jnz short Photosho.005D67F5 ; 没

005D67EA . 8A>mov cl,byte ptr ds:[esi+50]

005D67ED . 84>test cl,cl

005D67EF . 75>jnz short Photosho.005D67F5 ; 没

005D67F1 . 32>xor bl,bl

005D67F3 . EB>jmp short Photosho.005D67F7

005D67F5 > B3>mov bl,1

005D67F7 > 84>test al,al

005D67F9 . 74>je short Photosho.005D6808 ; 跳

005D67FB . 68>push FFFF9D22

005D6800 . E8>call Photosho.00B80070

005D6805 . 83>add esp,4

005D6808 > 84>test bl,bl

005D680A . 5B pop ebx

005D680B . 74>je short Photosho.005D6859 ; 跳

005D680D . E8>call Photosho.00B6E090

005D6812 . 50 push eax

005D6813 . 6A>push -1

005D6815 . 68>push Photosho.01187064 ; ASCII "$$$/ErrorStrings/MissingEnigmaLibsbecause certain required files were not found in the Adobe Photoshop 7.0 Required folder. Please reinstall Photoshop to restore these files"

005D681A . 8D>lea ecx,dword ptr ss:[esp+10]

005D681E . E8>call Photosho.00B72530

005D6823 . 8D>lea ecx,dword ptr ss:[esp+4]

005D6827 . C7>mov dword ptr ss:[esp+10],0

005D682F . 51 push ecx

005D6830 . E8>call Photosho.00B800D0

005D6835 . 83>add esp,4

005D6838 . 8D>lea ecx,dword ptr ss:[esp+4]

005D683C . C7>mov dword ptr ss:[esp+10],-1

005D6844 . E8>call Photosho.00B72800

005D6849 . 5E pop esi

005D684A . 8B>mov ecx,dword ptr ss:[esp+4]

005D684E . 64>mov dword ptr fs:[0],ecx

005D6855 . 83>add esp,10

005D6858 . C3 retn

005D6859 > 8A>mov al,byte ptr ds:[esi+52]

005D685C . 84>test al,al

005D685E 74>je short Photosho.005D686D ; 没（这里与日期2003年与2004年不一样）关键点

005D6860 . 68>push FFFF9D2C

005D6865 . E8>call Photosho.00B80070

005D686A . 83>add esp,4

修改：005D685E 74>je short Photosho.005D686D jmp即可！


第六篇 OllyDbg条件断点技巧

第二卷 OllyDBG 技巧系列 第六篇 OllyDbg条件断点技巧

作 者: pjb

先说一点 ALT+L 看记录~

在某条语句上按了SHIFT+F4后出现了个对话框,

1)第一行是"条件": 要输入的肯定是你感兴趣的条件啦,比如说下面的俩句

0041150 push eax

0041151 call [TranslateMessage]

你在0041150处下条件记录中断,假如这时候你要是想知道ESI等不等于0,那就可以设置条件 ESI0 或ESI!0 反正是关于ESI的条件了而不是只能利用这行的EAX. 当然一样可以在0041150处下断追消息,比如说你想知道按了 老鼠标移动消息,那么就设置 MSGWM_MOUSEMOVE 或是 MSG0200(移动的数字代码). 有人该问了,消息记录好象应该在下一行有函数的下断才正确吧?其实不一定非要在传递消息的那行0041151下断的,但在那行下断的好处是可以记录到函数参数,这在下面会讲到~

"条件"这的书写是按照 MASM32汇编的形式书写的,比如 ! > <

2)"说明""表达"

"解码表达式的值"

"说明"就是个注释啦,添不添无所谓了,除非你下的记录中断比较多,这样在记录里能看的明白~

"表达"其实就是我们记录的核心啦,程序在我们第1小条里的"条件"为真的时候,就会记录我们在"表达"里填的内容,比如说ESI0的时候,我们记录 [eax+4]的值

反正是你感兴趣的内容啦,若是想记录消息就添 MSG 啦,很多时候我们只添了"条件"而没有添"表达",程序中断后就会显示"未知的函数或标识符!",这就是原因了.

至于下面的"解码表达式的值"你可以随便选了,他不过是把记录后的数据又分析了一下, 比如说我们记录了[eax+4]的值是 201 ,那么当你选"信息代码(WM_XXX)"的时候那么你在记录(ALT+L看记录)里看到的就是被分析成了 201 WM_LBUTTONDOWN

当你选"布尔数值",那么记录里数值201后面就会跟个 TRUE了,其实就是OD对这个数据按照我们选择的类型又进行了一次分析而已~~

3)暂停程序 > 永不 条件满足时 永远

这三个选择就是条件为真记录表达的时候程序暂不暂停程序,那就看你调试的需要了~

4)记录表达式的值 > 永不 条件满足时 永远

呵呵,要是选"永不",那么我们设立记录"表达"还有啥意义啊, 若是选"永远",那么条件似乎不就没意义了,因为记录的时候已经不管条件了,只要运行到这就记录表达.不过好象也可以利用的,自己去想想啦..我不说.

一般都是选"条件满足"啦.

5)记录函数参数 > 永不 条件满足时 永远

这个要是你下在没函数的那行,那么这行就变灰了~~ 记录函数的参数很有意义啊,直接可以看到很多信息,我们比如说在有函数的那行下条件记录."条件"是 EAX0 "表达"是 [EAX+4],然后在这选择记录函数参数在"条件满足时";当然你若只想记录运行到此处的函数参数,那么就空着"条件"和"表达"吧,同时3)和4)的选择也要变为"永不"了...也许你在记录了啥也看不到,因为运行到这行可能EAX总不0 ,那么好换换 EAX>0 ,好象看了到一堆,然后还有PMSGXXXX HwXXXX,这就是运行到这里这个函数的参数啦~~ 我们也可以记录 CREATFILEA 函数,那么就知道都打开什么文件了~~

6) "运行次数":0为无限次

7) "如果程序暂停,发动如下参数到插件中..."

我不会,我就会上面那些,希望高手给补充一下,好让我们这些菜鸟学学~~

注意一点: 我们在OD菜单"查看">"窗口"里也可以用老鼠标右键给窗口过程下消息断点,下完后你可以用SHIFF_F4打开那个消息断点,发现它其实就是条件记录断点的一种形式而已~~~

举个例子吧:

比如说一个程序要求注册,点后出现一个注册窗口那么我们就要对点注册按钮下断,看看它之后执行了什么~~

注册窗口如果是对话框,那么肯定要用函数 CreatDialoParam或DialogBoxParam 若你对这俩函数都不了解的话,那就去看看书吧...首先用OD里的插件"窗口工具"获得注册按钮的标识(即ID),比如说是0422,记录下来备用啦~,

在OD里对这俩函数下断,然后执行程序,让注册窗口显示就会中断,OD里看堆栈,此函数的第4个参数pDlgProc就是对话框的过程入口了,这里是0041b0b0是入口

去对话框入口处,SHIFT+F4,在"条件"里输入 [ESP+8]0111 && [ESP+C]0422

"表达"可以空着,暂停程序在"条件满足时"

又该问了,为什么这么写? 请记住: 由于是过程调用,肯定要先向堆栈里压数据的,也就是标准说法:传递参数! 对于对话况过程或窗口过程,执行到入口处的时候堆栈内肯定压入的是MSG消息结构的数据,那么 [ESP+8]正好是WM_xxx消息,由于点了按钮我们肯定要抓的是WM_COMMAND消息 也就是111啦,这个不明白去看书~ [ESP+C]是Wparam参数,就是窗口的ID号了,这个条件就是当你按下了ID为422的注册按钮的时候中断,接下来你就可以单步看了~~ 当然,你用窗口工具的时候也能看到按钮的子窗口句柄,这时候 {ESP+10]就是它的数值了,也一样可以代替 {ESP+C]0422的条件了.其实都一样的,

友人该说了,不会用工具怎么办啊,那就直接在过程入口下SHIFT+F4啦,条件和表达都空着,只选记录函数参数,那么就能得到一堆记录了,找到显示有 WM_COMMAND的消息,把他的内容里的ID做为[ESP+C]的数值,一个个试啦


第一篇 轻松打造你自己的OllyDbg

第三卷 OllyDBG 改造系列 第一篇 轻松打造你自己的OllyDbg

作者：kongfoo

改掉OllyDbg标题

kongfoo/2003.12.10

目标：改掉OD窗口的caption和classname。

思路：在软件启动时改变创建窗口函数的参数。说实话，这样做是第一次，没经验啊，所以下断就下了一大堆：CallWindowProcA,CoInitialize,CoCreateInstance,CreateWindowExA,DefWindowProcA,DispatchMessageA,EnableWindow,LoadIconA,SetWindowLongA,SetWindowTextA,ShowWindow。宁可杀错，不可放过。

F9之后先在一个LoadIconA停下，记下地址，再F9，先看看启动程序会经过哪些断点，然后把多余的断点去掉，再分析...

很快就找到知道2个关键地方：433d46的LoadIconA和433f0f的CreateWindowExA：(LoadIconA载入图标之后就是创建窗体啦，下面是CreateWindowExA部分代码)

00433EE3 |. 6A 00 PUSH 0 ; /lParam NULL

00433EE5 |. 51 PUSH ECX ; |hInst > 00400000

00433EE6 |. 6A 00 PUSH 0 ; |hMenu NULL

00433EE8 |. 6A 00 PUSH 0 ; |hParent NULL

00433EEA |. 8B45 E8 MOV EAX,DWORD PTR SS:[EBP-18] ; |

00433EED |. 50 PUSH EAX ; |Height

00433EEE |. 8B55 EC MOV EDX,DWORD PTR SS:[EBP-14] ; |

00433EF1 |. 52 PUSH EDX ; |Width

00433EF2 |. 8D96 A3170000 LEA EDX,DWORD PTR DS:[ESI+17A3] ; |

00433EF8 |. 8B4D F0 MOV ECX,DWORD PTR SS:[EBP-10] ; |

00433EFB |. 51 PUSH ECX ; |Y

00433EFC |. 8D8E CD170000 LEA ECX,DWORD PTR DS:[ESI+17CD] ; |

00433F02 |. 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C] ; |

00433F05 |. 50 PUSH EAX ; |X

00433F06 |. 68 0000CF02 PUSH 2CF0000 ; |Style WS_OVERLAPPED|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_CLIPCHILDREN|WS_SYSMENU|WS_THICKFRAME|WS_CAPTION

00433F0B |. 52 PUSH EDX ; |WindowName 看到这2个没有？目标出现!

00433F0C |. 51 PUSH ECX ; |Class

跟一下就知道windowName放在4aeedf,class放在4aeeb5，用UE去相应的地方改一改就行啦

先用d 4aeedf，可以看到OLLYDBG字样，接住是Unable to create the main window，去UE查这个字串就可以找到WindowName啦

00433F0D |. 6A 10 PUSH 10 ; |ExtStyle WS_EX_ACCEPTFILES

00433F0F |. E8 6A3D0700 CALL <JMP.&USER32.CreateWindowExA> ; CreateWindowExA

UE中找Unable to create the main window,OLLYDBG的偏移量为ae4df，哪么class字串的偏移就是ae4b5了，

2个地方都改掉。好啦，现在你拥有自己的OllyDbg修改版啦

小结：

1、改主窗体标题、类名。

可以躲过FindWindow系列函数的检查。

2、改子窗体标题、类名。

和1的方法相同，作用一样。

3、改进程名。

即改文件名，改了后如果出现装载不了插件的情况可以用没改名的OD载入改名的OD，然后直接退出就行。

可以躲过CreateToolHelp等根据进程名的检查。

4、对于“...跟踪某些壳的程序仍然被发现，...而且有些程序一载入就运行，怎么也无法停止...”这个问题，我的OD也一样有这样的问题，呵呵，没时间深究，有兴趣的话你可以调试一下OD，跟一下它在载入程序时的动作，会有答案的。。。

5、除了FindWindow/CreateToolHelp找进程名等方法外，还有其它办法知道OD在运行的，呵呵...


第二篇 打造自己喜欢的 Ollydbg

第三卷 OllyDBG 改造系列 第二篇 打造自己喜欢的 Ollydbg

作者：askformore

上次上传了个英文版 OD1.10 的 DIY，感觉很粗糙，当时由于打包压缩时误删了笔记，所以只能上传成品附件，也没空重写。近来，有些时间就重来一次，把功能稍作完善一下，本想使菜单能看见工具的图标的，美化一下的，可是脑子里没门，现在也够用，就算了。

DIY 过程如下（下面的内容多数不是为新手准备的）：

首先，我们用LordPE加载 OD1.10 中文版，翻到 区段 和目录 部分，将重定位表偏移地址记住，然后擦除重定位表的所有相关信息，用16进制工具裁掉那个section的内容，因为我们要加入资源，而资源表又不是在最后的 section，或者你可以独立将重定位表保存起来，等 DIY 成功了再补回来也是可以的。另外，对于截获OD1.10的 消息流 的位置的方法，可参阅 pll621 老大的文章

在菜单资源上 添加需要的菜单“工具(&T)”，如下：

……

POPUP "帮助(&H)"

{

MENUITEM "版本信息(&A)", 2501

MENUITEM "帮助内容(&C)", 2502

MENUITEM SEPARATOR

MENUITEM "选择 API 帮助文件(&P)", 2503

MENUITEM "打开 API 帮助文件(&H)", 2504

}

POPUP "工具(&T)"

{

MENUITEM "自定义工具", 2509

MENUITEM SEPARATOR

MENUITEM "计算器", 2510

}

}

下面是我自己取名的对话窗口资源脚本： DIA_CFG_TOOLMENU

DIA_CFG_TOOLMENU DIALOG 80, 30, 271, 225

STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "配置工具菜单"

LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL

FONT 9, "宋体"

{

CONTROL "选择工具", 9099, BUTTON, BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 8, 9, 258, 191

CONTROL "", 9041, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 20, 160, 12

CONTROL "", 9042, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 38, 160, 12

CONTROL "", 9043, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 56, 160, 12

CONTROL "", 9044, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 74, 160, 12

CONTROL "", 9045, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 92, 160, 12

CONTROL "", 9046, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 110, 160, 12

CONTROL "", 9047, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 128, 160, 12

CONTROL "", 9048, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 146, 160, 12

CONTROL "", 9049, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 164, 160, 12

CONTROL "", 9050, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 182, 160, 12

CONTROL "Tool01:", 9021, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 13, 21, 30, 9

CONTROL "Tool02:", 9022, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 13, 39, 30, 9

CONTROL "Tool03:", 9023, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 13, 57, 30, 9

CONTROL "Tool04:", 9024, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 13, 75, 30, 9

CONTROL "Tool05:", 9025, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 13, 94, 30, 9

CONTROL "Tool06:", 9026, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 14, 112, 30, 9

CONTROL "Tool07:", 9027, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 13, 129, 30, 9

CONTROL "Tool08:", 9028, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 13, 147, 30, 9

CONTROL "Tool09:", 9029, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 13, 165, 30, 9

CONTROL "Tool10:", 9030, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 13, 183, 30, 9

CONTROL "更改", 9001, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 17, 50, 14

CONTROL "更改", 9002, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 36, 50, 14

CONTROL "更改", 9003, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 55, 50, 14

CONTROL "更改", 9004, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 72, 50, 14

CONTROL "更改", 9005, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 90, 50, 14

CONTROL "更改", 9006, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 108, 50, 14

CONTROL "更改", 9007, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 126, 50, 14

CONTROL "更改", 9008, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 144, 50, 14

CONTROL "更改", 9009, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 162, 50, 14

CONTROL "更改", 9010, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 209, 180, 50, 14

CONTROL "确定(&O)", 8888, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 74, 205, 50, 14

CONTROL "取消(&C)", 2, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 205, 50, 14

}

上面是一个我盗取一个游戏模拟器配置菜单的对话窗口模块，各控件名称和 ID（最好不存在冲突） 全改了，位置也微调好了，反正调整到自己满意就可以!

你自己可以在 ResHacker 上测试，可是用它进补成功的！

浏览资源得知，“确定”和“取消”的 ID 分别是 2 和 1 ，我这里确定改为 8888，希望它发落去!

我找了这对话窗口（消息流较少的进行“盗版”）--> “版本信息”菜单

代码:--------------------------------------------------------------------------------

00440C3C push ebp

00440C3D mov ebp,esp

00440C3F add esp,-480

00440C45 mov eax,dword ptr ss:[ebp+10]

00440C48 mov edx,dword ptr ss:[ebp+C]

00440C4B sub edx,110 ; Switch (cases 110..112)

00440C51 je short 00440C62 ; NEWHAND.00440C62

00440C53 dec edx

00440C54 je short 00440CB2 ; NEWHAND.00440CB2

00440C56 dec edx

00440C57 je 00440CF7 ; NEWHAND.00440CF7

00440C5D jmp 00440D0E ; NEWHAND.00440D0E

00440C62 mov byte ptr ss:[ebp-480],0 ; Case 110 (WM_INITDIALOG) of switch 00440C4B

00440C69 mov byte ptr ss:[ebp-440],0

00440C70 lea eax,dword ptr ss:[ebp-440]

00440C76 lea ecx,dword ptr ss:[ebp-480]

00440C7C push eax ; /Arg6

00440C7D push ecx ; |Arg5

00440C7E push 0A ; |Arg4 0000000A

00440C80 push 1 ; |Arg3 00000001

00440C82 lea eax,dword ptr ss:[ebp-400] ; |

00440C88 push 4B9073 ; |Arg2 004B9073 ASCII 0A,"NewHand v%"

00440C8D push eax ; |Arg1

00440C8E call 004A6C2C ; /NEWHAND.004A6C2C

00440C93 add esp,18

00440C96 lea edx,dword ptr ss:[ebp-400]

00440C9C push edx ; /Text

00440C9D push 0E75 ; |ControlID E75 (3701.)

00440CA2 mov ecx,dword ptr ss:[ebp+8] ; |

00440CA5 push ecx ; |hWnd

00440CA6 call 004AF58E ; /SetDlgItemTextA

00440CAB call 00546360 ; // 原指令 mov eax,1

00440CB0 jmp short 00440D10 ; NEWHAND.00440D10

00440CB2 mov edx,eax ; Case 111 (WM_COMMAND) of switch 00440C4B

00440CB4 and dx,0FFFF

00440CB9 cmp dx,1

00440CBD je short 00440CC5 ; NEWHAND.00440CC5

00440CBF cmp dx,2

00440CC3 jnz short 00440CD2 ; NEWHAND.00440CD2

00440CC5 push 0 ; /Result 0

00440CC7 mov ecx,dword ptr ss:[ebp+8] ; |

00440CCA push ecx ; |hWnd

00440CCB call 004AF3EA ; /EndDialog

00440CD0 jmp short 00440D0E ; NEWHAND.00440D0E

00440CD2 call 00546206 ; // 原指令 cmp dx,0e76

00440CD7 jnz short 00440D0E ; NEWHAND.00440D0E

00440CD9 push 0 ; /IsShown 0

00440CDB push 4B91A4 ; |DefDir "."

00440CE0 push 0 ; |Parameters NULL

00440CE2 push 4B917F ; |FileName "http://home.t-online.de/home/NewHand"

00440CE7 push 4B917A ; |Operation "open"

00440CEC mov eax,dword ptr ss:[ebp+8] ; |

00440CEF push eax ; |hWnd

00440CF0 call 004AF342 ; /ShellExecuteA

00440CF5 jmp short 00440D0E ; NEWHAND.00440D0E

00440CF7 and eax,0FFF0 ; Case 112 (WM_SYSCOMMAND) of switch 00440C4B

00440CFC cmp eax,0F060

00440D01 jnz short 00440D0E ; NEWHAND.00440D0E

00440D03 push 0 ; /Result 0

00440D05 mov edx,dword ptr ss:[ebp+8] ; |

00440D08 push edx ; |hWnd

00440D09 call 004AF3EA ; /EndDialog

00440D0E xor eax,eax ; Default case of switch 00440C4B

00440D10 mov esp,ebp

00440D12 pop ebp

00440D13 retn 10

00440D16 nop

00440D17 nop

00440D18 push 0 ; /lParam NULL

00440D1A mov eax,dword ptr ds:[4D3B80] ; |

00440D1F push 440C3C ; |DlgProc NEWHAND.00440C3C //借用一下这里

00440D24 push eax ; |hOwner > NULL

00440D25 mov edx,dword ptr ds:[4D3B78] ; |

00440D2B push 4B91A6 ; |pTemplate "DIA_ABOUT"

00440D30 push edx ; |hInst > NULL

00440D31 call 004AF3C6 ; /DialogBoxParamA

00440D36 retn--------------------------------------------------------------------------------

接下来为刚才处理过的 OD1.10 中文版增加一个 1000h 长度的 section，作为编写运行代码（数据）的空间

下面是 DIY 我们自己需要的代码，对于修改消息流的指令就不贴了，已有注释，代码很乱，边改边写的，没办法了，下面看到的 API 都是从OD1.10里面找到的。。。代码区定为 va: 546000 - 5463FF

代码:--------------------------------------------------------------------------------

00546000 nop

00546001 nop

00546002 nop

00546003 nop

00546004 nop

00546005 nop

00546006 nop

00546007 nop

00546008 nop

00546009 nop

0054600A nop

0054600B push ebp ; // 激活启动 计算器 功能

0054600C mov ebp,esp

0054600E pushad

0054600F add esp,-120

00546015 push 104

0054601A lea eax,dword ptr ss:[esp+4]

0054601E push eax

0054601F call 004AF0EA ; <jmp.&KERNEL32.GetWindowsDirectoryA>

00546024 add eax,esp

00546026 inc eax

00546027 mov dword ptr ds:[eax],636C6143

0054602D mov dword ptr ds:[eax+4],6578652>

00546034 mov byte ptr ds:[eax+8],0

00546038 mov ebx,esp

0054603A push 0

0054603C push ebx

0054603D push 0

0054603F push eax

00546040 push 4B917A ; ASCII "open"

00546045 mov eax,dword ptr ss:[ebp+8]

00546048 push eax

00546049 call 004AF342 ; <jmp.&SHELL32.ShellExecuteA>

0054604E add esp,120

00546054 popad

00546055 mov esp,ebp

00546057 pop ebp

00546058 jmp 00434212 ; // 返回执行结束的应该到达的地方

0054605D nop

0054605E cmp dx,9CE ; // 来自： 433A1F 的 sub edx,9C7

00546063 je short 0054600B ; // ID 相等 则跳往 执行 计算器

00546065 sub eax,9CD

0054606A je 005461E0 ; // 自定义工具菜单 ID

00546070 cmp eax,0B

00546073 ja short 0054607E ; // 是否 新添加菜单的 ID

00546075 cmp eax,2

00546078 jnb 005461A8 ; NEWHAND.005461A8

0054607E sub edx,9C7

00546084 jmp 00433A25 ; // 返回原来的 消息检测队列

00546089 nop

0054608A nop

0054608B nop

0054608C nop

0054608D nop

0054608E nop

0054608F nop

00546090 nop

00546091 nop

00546092 nop

00546093 nop

00546094 push ebx ; // 初始化“工具”菜单

00546095 push ebp

00546096 mov ebp,esp

00546098 xor ebx,ebx

0054609A push 0 ; // 为重建菜单准备

0054609C lea eax,dword ptr ds:[ebx+9CF]

005460A2 push eax

005460A3 mov edx,dword ptr ss:[ebp+4]

005460A6 push edx

005460A7 call 004AF55E ; <jmp.&USER32.RemoveMenu>

005460AC inc ebx

005460AD cmp ebx,0A

005460B0 jl short 0054609A ; NEWHAND.0054609A

005460B2 xor ebx,ebx

005460B4 xor edi,edi

005460B6 push 4D53A4 ; // Ollydbg.ini 路径

005460BB push 100 ; // 定义缓冲区大小

005460C0 push dword ptr ds:[546E14] ; // 缓冲区，用于读取所对应的数据

005460C6 push 4C1700

005460CB push dword ptr ds:[546E10] ; NEWHAND.00546E08

005460D1 push 546E00 ; ASCII "Tools"

005460D6 call 004AF08A ; <jmp.&KERNEL32.GetPrivateProfileStringA>

005460DB cmp eax,0

005460DE je short 0054614C ; NEWHAND.0054614C

005460E0 mov eax,dword ptr ds:[546E14]

005460E5 mov esi,eax

005460E7 push 0

005460E9 push 80

005460EE push 3

005460F0 push 0

005460F2 push 3

005460F4 push 0

005460F6 push eax

005460F7 call 004AEFCA ; <jmp.&KERNEL32.CreateFileA>

005460FC xor ebx,ebx

005460FE cmp eax,-1

00546101 je short 0054610A ; // 检测文件的存在有效性

00546103 inc ebx

00546104 push eax

00546105 call 004AEFB8 ; <jmp.&KERNEL32.CloseHandle>

0054610A cmp bl,1

0054610D je short 00546117 ; NEWHAND.00546117

0054610F mov eax,dword ptr ds:[546E14]

00546114 mov byte ptr ds:[eax],0

00546117 cmp byte ptr ds:[esi],0

0054611A je short 0054614C ; NEWHAND.0054614C

0054611C inc esi

0054611D cmp byte ptr ds:[esi],0

00546120 jnz short 0054611C ; NEWHAND.0054611C

00546122 dec esi

00546123 cmp byte ptr ds:[esi],5C

00546126 je short 0054612A ; NEWHAND.0054612A

00546128 jmp short 00546122 ; NEWHAND.00546122

0054612A mov byte ptr ds:[esi],0

0054612D inc esi

0054612E nop

0054612F nop

00546130 nop

00546131 nop

00546132 nop

00546133 nop

00546134 nop

00546135 nop

00546136 nop

00546137 nop

00546138 nop

00546139 push esi

0054613A lea eax,dword ptr ds:[edi+9CF] ; // 根据消息 ID 偏移量进行计算

00546140 push eax

00546141 push 0

00546143 mov ecx,dword ptr ss:[ebp+4]

00546146 push ecx

00546147 call 004AF34E ; <jmp.&USER32.AppendMenuA>

0054614C inc edi

0054614D add byte ptr ds:[546E0D],1 ; // 调整 Key

00546154 nop

00546155 nop

00546156 nop

00546157 nop

00546158 nop

00546159 nop

0054615A add dword ptr ds:[546E14],100 ; // 调整对应的缓冲区指针

00546164 cmp edi,0A

00546167 jl 005460B6 ; // 10 个为限

0054616D mov esp,ebp

0054616F pop ebp

00546170 pop ecx

00546171 call 00546370 ; // 还原初始变量值（全局）

00546176 jmp 00434247 ; // 返回循环点

0054617B nop

0054617C nop

0054617D nop

0054617E nop

0054617F nop

00546180 nop

00546181 nop

00546182 nop

00546183 nop

00546184 nop

00546185 nop

00546186 cmp ax,9CD ; // 来自 43359C 的 jne 434247

0054618A jnz 00434247 ; NEWHAND.00434247

00546190 call 00546370 ; // 还原初始变量值（全局）

00546195 mov edi,546400

0054619A mov ecx,0A00

0054619F xor eax,eax

005461A1 rep stos byte ptr es:[edi]

005461A3 jmp 00546094 ; NEWHAND.00546094

005461A8 sub al,2 ; // 执行 exe 工具

005461AA imul ebx,eax,100

005461B0 add ebx,dword ptr ds:[546E14] ; NEWHAND.00546400

005461B6 mov ecx,ebx

005461B8 inc ecx

005461B9 cmp byte ptr ds:[ecx],0

005461BC jnz short 005461B8 ; NEWHAND.005461B8

005461BE inc ecx

005461BF nop

005461C0 push 0

005461C2 push ebx

005461C3 push 0

005461C5 push ecx

005461C6 push 4B917A ; ASCII "open"

005461CB mov eax,dword ptr ss:[ebp+8]

005461CE push eax

005461CF call 004AF342 ; <jmp.&SHELL32.ShellExecuteA>

005461D4 jmp 00434212 ; NEWHAND.00434212

005461D9 nop

005461DA nop

005461DB nop

005461DC nop

005461DD nop

005461DE nop

005461DF nop

005461E0 push 434212 ; // 压入返回地址

005461E5 push 0

005461E7 mov eax,dword ptr ds:[4D3B80]

005461EC push 440C3C

005461F1 push eax

005461F2 mov edx,dword ptr ds:[4D3B78]

005461F8 push 4B8FDA ; ASCII "DIA_CFG_TOOLMENU"

005461FD push edx

005461FE call 004AF3C6 ; <jmp.&USER32.DialogBoxParamA>

00546203 retn

00546204 nop

00546205 nop

00546206 cmp dx,0E76 ; // 来自 440CD2 的 cmp dx,0e76

0054620B jnz short 0054620E ; NEWHAND.0054620E

0054620D retn

0054620E cmp dx,22B8

00546213 je 0054630E ; // 是否点击了DIY的 “确定”按钮

00546219 sub eax,2328

0054621E cmp eax,0A

00546221 ja short 00546298 ; NEWHAND.00546298

00546223 cmp eax,1

00546226 jb short 00546298 ; NEWHAND.00546298

00546228 nop ; // 是否点击了DIY的 “更改”按钮

00546229 nop

0054622A pushad

0054622B push eax

0054622C push 546E20 ; // 结构

00546231 call 004AF268 ; <jmp.&COMDLG32.GetOpenFileNameA>

00546236 test eax,eax

00546238 jnz short 0054623F ; NEWHAND.0054623F

0054623A pop eax

0054623B jmp short 00546297 ; // 没有选择则不登记，直接回到 消息队列

0054623D nop

0054623E nop

0054623F push 2030

00546244 push 546E9F ; // 标题

00546249 push 546EAA ; // 提示 更改信息

0054624E mov eax,dword ptr ds:[4D3B7C]

00546253 push eax

00546254 call 004AF516 ; <jmp.&USER32.MessageBoxA>

00546259 pop eax

0054625A nop

0054625B nop

0054625C dec eax ; // ***这里不即时登记 “更新”也是可以的

0054625D add al,30 ; // 计算所对应的 工具序列

0054625F mov byte ptr ds:[546E0D],al

00546264 push 4D53A4

00546269 push dword ptr ds:[546E18] ; NEWHAND.00546F00

0054626F push dword ptr ds:[546E10] ; // ***登记所选择的

00546275 push 546E00 ; ASCII "Tools"

0054627A call 004AF21C ; <jmp.&KERNEL32.WritePrivateProfileStringA>

0054627F popad

00546280 pushad

00546281 lea ecx,dword ptr ds:[eax+2350] ; // 对应的 Edit 控件 id

00546287 mov eax,dword ptr ds:[546E18] ; // 对应的工具路径

0054628C mov edx,dword ptr ss:[ebp+8] ; // 窗口句柄

0054628F push eax

00546290 push ecx

00546291 push edx

00546292 call 004AF58E ; // 更新对应的 Edit 控件内容

00546297 popad

00546298 mov al,1

0054629A test al,al

0054629C retn

0054629D nop

0054629E nop

0054629F nop

005462A0 nop

005462A1 nop

005462A2 nop

005462A3 pushad ; // 初始化对话框 Edit控件 的内容

005462A4 xor edi,edi

005462A6 lea eax,dword ptr ds:[edi+30]

005462A9 mov byte ptr ds:[546E0D],al

005462AE push 4D53A4

005462B3 push 100

005462B8 push dword ptr ds:[546E14] ; // 缓冲区读取所对应的数据

005462BE push 4C1700

005462C3 push dword ptr ds:[546E10] ; NEWHAND.00546E08

005462C9 push 546E00 ; ASCII "Tools"

005462CE call 004AF08A ; <jmp.&KERNEL32.GetPrivateProfileStringA>

005462D3 cmp al,0

005462D5 je short 005462ED ; // 为 0 不进行刷新数据

005462D7 lea ecx,dword ptr ds:[edi+2351]

005462DD mov eax,dword ptr ds:[546E14]

005462E2 mov edx,dword ptr ss:[ebp+8]

005462E5 push eax ; // 下面是读取并设置各工具菜单路径

005462E6 push ecx

005462E7 push edx

005462E8 call 004AF58E ; <jmp.&USER32.SetDlgItemTextA>

005462ED inc edi

005462EE add dword ptr ds:[546E14],100 ; // 调整对应的缓冲区指针

005462F8 cmp edi,0A

005462FB jl short 005462A6 ; // 10 个为限

005462FD popad

005462FE retn

005462FF nop

00546300 nop

00546301 nop

00546302 nop

00546303 nop

00546304 nop

00546305 nop

00546306 nop

00546307 nop

00546308 nop

00546309 nop

0054630A nop

0054630B nop

0054630C nop

0054630D nop

0054630E pushad ; // 点按了“确定”按钮，登记所有对应的内容

0054630F xor edi,edi

00546311 lea eax,dword ptr ds:[edi+30]

00546314 mov byte ptr ds:[546E0D],al

00546319 lea ecx,dword ptr ds:[edi+2351]

0054631F mov eax,dword ptr ds:[546E18]

00546324 mov edx,dword ptr ss:[ebp+8]

00546327 push 100

0054632C push eax

0054632D push ecx

0054632E push edx

0054632F call 004AF45C ; <jmp.&USER32.GetDlgItemTextA>

00546334 push 4D53A4

00546339 push dword ptr ds:[546E18] ; NEWHAND.00546F00

0054633F push dword ptr ds:[546E10] ; NEWHAND.00546E08

00546345 push 546E00 ; ASCII "Tools"

0054634A call 004AF21C ; <jmp.&KERNEL32.WritePrivateProfileStringA>

0054634F inc edi

00546350 cmp edi,0A

00546353 jl short 00546311 ; NEWHAND.00546311

00546355 popad

00546356 pop eax

00546357 jmp 00440CC5 ; // 返回到 点击“确定”按钮应该返回的地方

0054635C nop

0054635D nop

0054635E nop

0054635F nop

00546360 cmp al,1

00546362 jnz 005462A0 ; // 由于借用别的 Dia 的事件，当它初始失败，al为0，就是应该属于我们的初始化

00546368 retn

00546369 nop

0054636A nop

0054636B nop

0054636C nop

0054636D nop

0054636E nop

0054636F nop

00546370 mov byte ptr ds:[546E0D],30 ; // 还原变量初值

00546377 mov dword ptr ds:[546E14],546400

00546381 retn

00546382 nop

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

Ok，数据区定为 va: 546400 - 546FFF，以下是部分初始的数据区：

代码:--------------------------------------------------------------------------------

Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F

变量和指针：

00105400 54 6F 6F 6C 73 00 00 00 54 6F 6F 6C 5B 30 5D 00 Tools...Tool[0].

00105410 08 6E 54 00 00 64 54 00 00 6F 54 00 00 00 00 00 .nT..dT..oT.....

OpenFileNameA结构：

00105420 4C 00 00 00 00 00 00 00 00 00 00 00 70 6E 54 00 L...........pnT.

00105430 00 00 00 00 00 00 00 00 00 00 00 00 00 6F 54 00 .............oT.

00105440 04 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

00105450 90 6E 54 00 06 28 28 00 00 00 00 00 00 00 00 00 恘T..((.........

00105460 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

字符串常量：

00105470 BF C9 D6 B4 D0 D0 CE C4 BC FE 20 28 2A 2E 65 78 可执行文件 (*.ex

00105480 65 29 00 2A 2E 65 78 65 00 00 00 00 00 00 00 00 e).*.exe........

00105490 D1 A1 D4 F1 65 78 65 B9 A4 BE DF 00 00 00 00 B8 选择exe工具....?

001054A0 FC B8 C4 CC E1 CA BE A3 BA 00 C4 E3 B5 C4 B8 FC 奶崾荆?你的更

001054B0 B8 C4 BC B4 BD AB BC A4 BB EE A3 A1 00 00 00 00 改即将激活！....

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

注意找出主窗口句柄（参看一下其它窗口使用的函数参数是如何实现叫出来的，模拟一下指令就行了），测试中注意堆栈平衡，这个出错会很麻烦的，修修补补到现在，保存所有，运行测试通过了！特写下这篇文章，以慰已劳，Enjoy!


第三篇 打造对抗 CMPXCHG8G的OD

第三卷 OllyDBG 改造系列 第三篇 打造对抗 CMPXCHG8G的OD

作者：simonzh2000

现在好多壳软件都使用反调试技术, 我们需要对OD动点手术,才能顺利调试.

我们整天用OD调试其他软件, 今天也让他尝尝被调试的滋味. hehe.

1. 修改 Window Caption 和 ClassName

这一步就不多说了, 将 Ollydbg.exe 拷贝到 CMD.EXe, 用 UltraEdit 打开CMD.EXE,

搜索 OllyDBG 字符, 替换为个性化字符, 下面以 ZSMOZSM 为例

2. 复制 Ollydgb.ini 到 ZSMOZSM.ini

这样一来, 你原来的配置就生效了, 以后也不需要 Ollydbg.exe 了

3. 最近好多壳开始利用 CMPXCHG8B 非法指令, 弹出警告, 去掉他.

From Intel

CMPXCHG8B—Compare and Exchange 8 Bytes

Opcode Instruction Description

0F C7 /1 m64 CMPXCHG8B m64 Compare EDX:EAX with m64. If equal, set ZF and load

ECX:EBX into m64. Else, clear ZF and load m64 into EDX:EAX.

Description

Compares the 64-bit value in EDX:EAX with the operand (destination operand). If the values

are equal, the 64-bit value in ECX:EBX is stored in the destination operand. Otherwise, the

value in the destination operand is loaded into EDX:EAX. The destination operand is an 8-byte

memory location. For the EDX:EAX and ECX:EBX register pairs, EDX and ECX contain the

high-order 32 bits and EAX and EBX contain the low-order 32 bits of a 64-bit value.

This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.

To simplify the interface to the processor’s bus, the destination operand receives a write

cycle without regard to the result of the comparison. The destination operand is written back if

the comparison fails; otherwise, the source operand is written into the destination. (The

processor never produces a locked read without also producing a locked write.)

Intel Architecture Compatibility

This instruction is not supported on Intel processors earlier than the Pentium processors.

从上面最后一行, 我们可以看到, 这条指令不能在 Pentium 以前的 CPU 上使用,

所以OD有一个警告框. 其实是多此一举, 现在谁还用 486 啊?

让我们去掉他.

Linson 的新版仙剑刚刚出炉, 里面也有CMPXCHG8B, 我们就用他吧, Main.exe.

运行 Ollydbg.exe, 打开 CMD.EXE , F9, 现在桌面有两个 OD,

Ollydbg 是调试器, CMD.exe 就是被调试的程序, 别搞错了.

在 Ollydbg 里 Search For Name(label) in Current Module -- Name窗口, MessageBoxA ,Enter -- References窗口

有好多个对 MessageBoxA 的调用, Set BreakPoint on Every Command

切换到 CMD.EXE, 打开 Main.EXE, 被 Ollydbg.exe 断在这里

0045CC20 |. 6A 30 PUSH 30 ; /Style MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL

0045CC22 |. 68 6A9F4B00 PUSH CMD.004B9F6A ; |Title "Entry Point Alert"

0045CC27 |. 50 PUSH EAX ; |Text

0045CC28 |. 52 PUSH EDX ; |hOwner > 000A00CC (\'zsmozsm - Main.exe - [CPU]\',class\'zsmozsm\')

0045CC29 |. E8 84100500 CALL <JMP.&USER32.MessageBoxA> ; MessageBoxA

这个就是 "Entry Point Alert" 对话框, F2取消断点, F9, 切换到 CMD.EXE, 点OK, 停在 Main.exe 入口点 412060 , F9,

再次被 OllyDBG.EXE 断下, 4340B8, 就是那个烦人的警告了, 去掉其他的断点.

0043402A |. 6A 00 PUSH 0 ; /Arg7 00000000

0043402C |. 8D8D 50F4FFFF LEA ECX,DWORD PTR SS:[EBP-BB0] ; |

00434032 |. 6A 01 PUSH 1 ; |Arg6 00000001

00434034 |. 51 PUSH ECX ; |Arg5 12E8B8

00434035 |. 6A 00 PUSH 0 ; |Arg4 00000000

00434037 |. 53 PUSH EBX ; |Arg3 00412167 (Main.exe 中 CMPXCHG8B 的 VA)

00434038 |. 8B45 E8 MOV EAX,DWORD PTR SS:[EBP-18] ; |

0043403B |. 8D95 CCFDFFFF LEA EDX,DWORD PTR SS:[EBP-234] ; |

00434041 |. 50 PUSH EAX ; |Arg2 10h, 机器码长度最长 16 字节

00434042 |. 52 PUSH EDX ; |Arg1 12F234, 放 Main.EXE 中 412167 开始的 16 字节

00434043 |. E8 741EFEFF CALL CMD._Disasm ; _Disasm

; // 这个 CALL 将分析 412167 开始的 16 字节,

; // 找出最合理的一条机器指令, 返回机器指令长度

00434048 |. 83C4 1C ADD ESP,1C

0043404B |. 8945 E8 MOV DWORD PTR SS:[EBP-18],EAX ; // 指令长度

0043404E |. 837D 10 00 CMP DWORD PTR SS:[EBP+10],0 ; // 判断是不是危险指令, 见 _Disasm 原程序.

00434052 |. 74 76 JE SHORT CMD.004340CA ; // 将这条语句改成 JMP 4340CA, (74->EB), 就可以了

00434054 |. F685 8DFAFFFF>TEST BYTE PTR SS:[EBP-573],30

0043405B |. 74 6D JE SHORT CMD.004340CA

0043405D |. 80BD 54F8FFFF>CMP BYTE PTR SS:[EBP-7AC],0

00434064 |. 75 04 JNZ SHORT CMD.0043406A

00434066 |. 33C0 XOR EAX,EAX

00434068 |. EB 15 JMP SHORT CMD.0043407F

0043406A |> 80BD 54F9FFFF>CMP BYTE PTR SS:[EBP-6AC],0

00434071 |. 75 07 JNZ SHORT CMD.0043407A

00434073 |. B8 01000000 MOV EAX,1

00434078 |. EB 05 JMP SHORT CMD.0043407F

0043407A |> B8 02000000 MOV EAX,2

0043407F |> C1E0 08 SHL EAX,8

00434082 |. 8D95 54F7FFFF LEA EDX,DWORD PTR SS:[EBP-8AC]

00434088 |. 03C2 ADD EAX,EDX

0043408A |. 53 PUSH EBX ; /Arg4

0043408B |. 50 PUSH EAX ; |Arg3

0043408C |. 8D8D DCFDFFFF LEA ECX,DWORD PTR SS:[EBP-224] ; |

00434092 |. 68 B4484B00 PUSH CMD.004B48B4 ; |Arg2 004B48B4 ASCII "%s Do you REALLY want to execute this code at address %08X?"

00434097 |. 51 PUSH ECX ; |Arg1

00434098 |. E8 2B130700 CALL CMD.004A53C8 ; CMD.004A53C8, sprintf()生成字符串

0043409D |. 83C4 10 ADD ESP,10

004340A0 |. 8D85 DCFDFFFF LEA EAX,DWORD PTR SS:[EBP-224]

004340A6 |. 8B15 F4174D00 MOV EDX,DWORD PTR DS:[4D17F4]

004340AC |. 68 34010000 PUSH 134 ; /Style MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2|MB_APPLMODAL

004340B1 |. 68 F0484B00 PUSH CMD.004B48F0 ; |Title "Dangerous command"

004340B6 |. 50 PUSH EAX ; |Text

004340B7 |. 52 PUSH EDX ; |hOwner > 000A00CC (\'zsmozsm - Main.exe - [CPU - m...\',class\'zsmozsm\')

004340B8 |. E8 F59B0700 CALL <JMP.&USER32.MessageBoxA> ; MessageBoxA

004340BD |. 83F8 06 CMP EAX,6 ; // 用户点 Yes, 继续执行

004340C0 74 08 JE SHORT CMD.004340CA

004340C2 |. 83C8 FF OR EAX,FFFFFFFF

004340C5 |. E9 F8060000 JMP CMD.004347C2

004340CA |> 8BBD 54FAFFFF MOV EDI,DWORD PTR SS:[EBP-5AC] ; // 这里执行

004340D0 |. 81E7 F0000000 AND EDI,0F0

004340D6 |. 837D 10 02 CMP DWORD PTR SS:[EBP+10],2

修改 434052 处 74 为 EB, Copy to Execute, 保存文件 CMD2.EXE , 退出ollydbg.EXE.

用 CMD2.EXe 打开 Main.EXE , 忽略所有异常, F9,

在 412167 处异常, ExceptionCode C000001E

Debug Option -- Exception -- Add last Exception -- Ignore custom Exception,

Shift + F9, 这个异常要经过好多次, 等一会儿, 仙剑就运行了.


第四篇 藏好自己的 OllyDbg

第三卷 OllyDBG 改造系列 第四篇 藏好自己的 OllyDbg

作者：forgot

OllyDbg 的检测方法多如繁星，即使悉数了解并掌握破解技巧也需要耗费一定的时间。

因此可以制作一个 DLL（比如写一个插件） 借助 API Hook 来完成自动隐藏工作。

这篇教程中我不打算介绍如何进行 Hook API，Google 会比我讲的详细 :)

我们的 DLL 面临着这些问题：

1) 躲避字符串解析漏洞

某天你拿到一个程序载入运行，OllyDbg 连续三次非法后伤逝……

基本可以估计这个程序钻了这个漏洞。OllyDbg 使用了格式化字符输出调试信息，

如果调试信息里带有格式控制字符譬如“%s%s%s”，在字符输出之前就会死于非命。

利用代码：

@pushsz "%s%s%s%s%s"

call OutputDebugStringA

要避免也很简单，只要钩住这个函数过滤掉对它的调用即可。想做的锦上添花可重定向输出到一个watch窗口中。

2) GetProcessHeap 后门

自从某人从 ASProtect 早期版本得到启发，发现 M$ 的此内存初值后门，这个漏洞也（将会）有不少人用。

利用代码：

call GetProcessHeap

mov eax, [eax+0Ch]

test eax, 40h

je no_debugger

我很抱歉，关于原因的事情你得问微软去了，谁叫你用 Windows 呢 ;-)

解决它要彻底些，不能用 Hook ，因为不一定非通过这个函数读取 Heap，PEB 也可以。

看看 GetProcessHeap：

7C80AA49 > 64:A1 18000000 mov eax, fs:[18]

7C80AA4F 8B40 30 mov eax, [eax+30]

7C80AA52 8B40 18 mov eax, [eax+18]

7C80AA55 C3 retn

剩下的不用多费唇舌了吧。

3) 卑鄙的 native API

看雪精华里 blowfish 详细介绍过 ZwSetInformationThread 等 API，最好统统 Hook。

4) MeltICE

如果你用监视器，就像 RegMon 等，需要照顾一下这里。

如果发现 CreateFileA/W 中的文件名开头是 “//./ ”决不能手软，截留返回0。

这些通常是要进行 MeltICE 探测的。不过有些注册程序要读取硬盘序列号也会打开类似上面的文件名，

所以还是建议你准备一份常用 MeltICE 名表，对比拦截，比较稳妥。

5) IsDebuggerPresent

这个函数你一定用过。

7C812E03 > 64:A1 18000000 mov eax, fs:[18]

7C812E09 8B40 30 mov eax, [eax+30]

7C812E0C 0FB640 02 movzx eax, byte ptr [eax+2]

7C812E10 C3 retn

修改掉标志就行了。

6) 进程检测

查阅一下 Process32Next 的帮助，如果发现它正探测你的调试器，把这个结构指向 Explorer 的或者直接跳到下一个。

现在可以不用把调试器改成丑陋的 eXpLoReR.eXe

7) 模块检测

看来 DBGHELP.DLL 不太受欢迎，有些程序调用 Module32Next检测到别人加载此模块就会大发雷霆……

8) UnhandledExceptionHandler

明明没有 SEH 处理程序缺能在没有调试器的时候运行，怎么也过不去怎么办？

看看 simonzh2000 《SetUnhandledExceptionFilter 的讨论》。

如果你上面处理了 native API 事实上这里不会再有问题。

9) BlockInput

最近在某个壳里看到这个函数：

push TRUE/FALSE

call BlockInput

就可以控制你的鼠标键盘是否能动了，按 Ctrl+Alt+Del 可以解，

不过谁也不想一按 F9 什么都不动了（做个绘图覆盖在调试器上面制造假死）

脑袋缺筋的可能会重启……

类似的还有 EnableWindow，不能让它肆意操作你的调试窗口。

10) 待你补充

我一时想不出更多阴招了。

forgot/iPB/Saga(他们死活不支持改名，又反对uS，就用Saga吧)


第五篇 OD复制BUG分析和修正

第三卷 OllyDBG 改造系列 第五篇 OD复制BUG分析和修正

作者：nbw

使用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 ................

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

多谢！


第六篇 打造处理 OutputDebugString("%s")的OD

第三卷 OllyDBG 改造系列 第六篇 打造处理 OutputDebugString("%s")的OD

作者：simonzh2000

最近好多壳利用 OutputDebugString 函数来对付 OD, 当然处理方法也比较简单, 更有大侠出了修改版 OD.

不过我这个人比较喜欢寻根究底, 想看看 OD 的毛病出在哪里.

1. 基础知识 from MSDN

下面是调试器和被调试进程之间的关系, 调试器用 CreateProcess 创建被调试进程.

被调试进程运行中会遇到 9 种需要向调试器汇报的事件, 调试器处理好后, 被调试进程才能继续.

平时我们最关心 Exception, 今天我们的主角是 DebugString.

EXCEPTION_DEBUG_EVENT equ 1

CREATE_THREAD_DEBUG_EVENT equ 2

CREATE_PROCESS_DEBUG_EVENT equ 3

EXIT_THREAD_DEBUG_EVENT equ 4

EXIT_PROCESS_DEBUG_EVENT equ 5

LOAD_DLL_DEBUG_EVENT equ 6

UNLOAD_DLL_DEBUG_EVENT equ 7

OUTPUT_DEBUG_STRING_EVENT equ 8

RIP_EVENT equ 9

while(WaitForDebugEvent(&DBEvent, INFINITE))

{

if (DBEvent.dwDebugEventCodeEXCEPTION_DEBUG_EVENT)

{

// 子进程遇到一个异常

}

else if (DBEvent.dwDebugEventCodeOUTPUT_DEBUG_STRING_EVENT)

{

// 子进程执行了 OutputDebugString 函数

}

.... // 共有 9 种情形

ContinueDebugEvent(DBEvent.dwProcessId, DBEvent.dwThreadId, dwContinueStatus);

}

typedef struct _DEBUG_EVENT { // de

DWORD dwDebugEventCode;

DWORD dwProcessId;

DWORD dwThreadId;

union {

EXCEPTION_DEBUG_INFO Exception;

CREATE_THREAD_DEBUG_INFO CreateThread;

CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;

EXIT_THREAD_DEBUG_INFO ExitThread;

EXIT_PROCESS_DEBUG_INFO ExitProcess;

LOAD_DLL_DEBUG_INFO LoadDll;

UNLOAD_DLL_DEBUG_INFO UnloadDll;

OUTPUT_DEBUG_STRING_INFO DebugString;

RIP_INFO RipInfo;

} u;

} DEBUG_EVENT;

typedef struct _OUTPUT_DEBUG_STRING_INFO { // odsi

LPSTR lpDebugStringData;

WORD fUnicode;

WORD nDebugStringLength;

} OUTPUT_DEBUG_STRING_INFO;

int vsprintf( char *buffer, const char *format, va_list argptr ); 这个是错误的根源

2. 测试用的程序

// TEST1.EXE

.386

.model flat, stdcall

option casemap:none

include c:/masm32/include/kernel32.inc

include c:/masm32/include/user32.inc

includelib c:/masm32/lib/kernel32.lib

includelib c:/masm32/lib/user32.lib

include c:/masm32/include/windows.inc

.const

szOutput db "%s",0

szMessage db "The program is fine", 0

szTitle db "Test",0

szFormat db "%s",0

.data?

szBuffer db 20 dup (?)

;------------ CODE ----------

.code

main:

invoke wsprintf, offset szBuffer, offset szFormat, offset szMessage

invoke OutputDebugString, offset szOutput

invoke MessageBox, 0, offset szBuffer, offset szTitle, MB_OK

invoke ExitProcess,0

end main

// End

用 OD 调试 test1.exe, 执行完 OutputDebugString, OD 左下角出现了 "Debug string: "

// Test2.EXE

其余不变, 只变一句

szOutput db "%s%s",0

用 OD 调试 test2.exe, 执行 OutputDebugString, OD 在 4a74cf 出现访问 [00000260]异常

// Test3.EXE

其余不变, 只变一句

szOutput db "%s%s%s",0

用 OD 调试 test2.exe, 执行 OutputDebugString, OD 在 4a74cf 出现访问 [00000001]异常

为什么 Test1 没问题, Test2, Test3 有问题? Test2 和 Test3 有区别吗? Let\'s go

3. 我们来看看 OD 是如何处理的

将 Ollydbg.exe 拷贝到 CMD.EXe, 运行 CMD.exe, 菜单 File - Open - Ollydbg.EXE ,

这样 CMD.exe 是调试器, Ollydbg.exe 就是被调试的程序, 别搞错了.

找 WaitForDebugEvent, 比较容易找到处理 DebugString 的地方, CMD 下断 431276.

F9 运行 Ollydbg.exe, 再用 Ollydbg.exe 打开 TEST3.EXE, F9 运行, CMD 断下, F7 跟进

00439616 > /6A 00 PUSH 0 ; /Timeout 0. ms

00439618 . 68 14574D00 PUSH OLLYDBG.004D5714 ; |pDebugEvent OLLYDBG.004D5714

0043961D . E8 E85B0700 CALL <JMP.&KERNEL32.WaitForDebugEvent> ; /WaitForDebugEvent

00439622 . 85C0 TEST EAX,EAX

00439624 . 75 44 JNZ SHORT OLLYDBG.0043966A

...

0043977D . 51 PUSH ECX ; /Arg1

0043977E . E8 4D54FFFF CALL OLLYDBG.0042EBD0 ; /OLLYDBG.0042EBD0, F7 跟进

0042EBD0 /$ 55 PUSH EBP

0042EBD1 |. 8BEC MOV EBP,ESP

0042EBD3 |. 81C4 04F0FFFF ADD ESP,-0FFC

0042EBD9 |. 50 PUSH EAX

0042EBDA |. 81C4 00F5FFFF ADD ESP,-0B00

0042EBE0 |. 53 PUSH EBX

0042EBE1 |. 56 PUSH ESI

0042EBE2 |. 57 PUSH EDI

0042EBE3 |. 8B35 1C574D00 MOV ESI,DWORD PTR DS:[4D571C] ; dwThreadID

0042EBE9 |. 56 PUSH ESI

0042EBEA |. E8 5DF8FFFF CALL OLLYDBG.0042E44C

0042EBEF |. 8BF8 MOV EDI,EAX

0042EBF1 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]

0042EBF4 |. 59 POP ECX

0042EBF5 |. 8938 MOV DWORD PTR DS:[EAX],EDI

0042EBF7 |. 8B15 14574D00 MOV EDX,DWORD PTR DS:[4D5714] ; pDebugEvent

0042EBFD |. 83FA 09 CMP EDX,9 ; Switch (cases 1..9) 被调试进程遇到的9种情况

0042EC00 |. 0F87 EE270000 JA OLLYDBG.004313F4

0042EC06 |. FF2495 0DEC42>JMP DWORD PTR DS:[EDX*4+42EC0D]

0042EC0D |. F4134300 DD OLLYDBG.004313F4 ; Switch table used at 0042EC06

0042EC11 |. 35EC4200 DD OLLYDBG.0042EC35

0042EC15 |. FF0C4300 DD OLLYDBG.00430CFF

0042EC19 |. D70D4300 DD OLLYDBG.00430DD7

0042EC1D |. 3F0F4300 DD OLLYDBG.00430F3F

0042EC21 |. 37104300 DD OLLYDBG.00431037

0042EC25 |. 2D114300 DD OLLYDBG.0043112D

0042EC29 |. B7114300 DD OLLYDBG.004311B7

0042EC2D |. 76124300 DD OLLYDBG.00431276 ; OUTPUT_DEBUG_STRING_EVENT 在 431276 处理

0042EC31 |. C7134300 DD OLLYDBG.004313C7

00431276 |> /830D 74574D00>OR DWORD PTR DS:[4D5774],4 ; Case 8 of switch 0042EBFD

0043127D |. 68 0A674B00 PUSH OLLYDBG.004B670A ; /Arg2 004B670A ASCII "Debug string: "

00431282 |. 8D95 98FDFFFF LEA EDX,DWORD PTR SS:[EBP-268] ; |

00431288 |. 52 PUSH EDX ; |Arg1 12F340

00431289 |. E8 9E590700 CALL OLLYDBG.004A6C2C ; /int sprintf(char *buffer,const char *format,...)

0043128E |. 83C4 08 ADD ESP,8

00431291 |. 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX ; "Debug string: " 的长度

00431294 |. 0FB71D 26574D>MOVZX EBX,WORD PTR DS:[4D5726] ; debug_string 的长度

0043129B |. 85DB TEST EBX,EBX

0043129D |. 7D 0A JGE SHORT OLLYDBG.004312A9

0043129F |. B8 01000000 MOV EAX,1

004312A4 |. E9 7C010000 JMP OLLYDBG.00431425

004312A9 |> 66:833D 24574>CMP WORD PTR DS:[4D5724],0 ; 是否 unicode ?

004312B1 |. 74 6B JE SHORT OLLYDBG.0043131E ; 由于我们使用了 OutputDebugStringA, 不是 unicode

0043131E |> BA 00010000 MOV EDX,100 ; 包括 "Debug string: " 最长 100h 长度

00431323 |. 2B55 F4 SUB EDX,DWORD PTR SS:[EBP-C]

00431326 |. 4A DEC EDX

00431327 |. 3BDA CMP EBX,EDX

00431329 |. 7E 09 JLE SHORT OLLYDBG.00431334

0043132B |. BB 00010000 MOV EBX,100

00431330 |. 2B5D F4 SUB EBX,DWORD PTR SS:[EBP-C]

00431333 |. 4B DEC EBX

00431334 |> 6A 03 PUSH 3 ; /Arg4 00000003

00431336 |. 53 PUSH EBX ; |Arg3 长度

00431337 |. A1 20574D00 MOV EAX,DWORD PTR DS:[4D5720] ; |

0043133C |. 50 PUSH EAX ; |Arg2 > 00402018 debug_string 在被调试进程中的地址

0043133D |. 8D95 98FDFFFF LEA EDX,DWORD PTR SS:[EBP-268] ; |

00431343 |. 0355 F4 ADD EDX,DWORD PTR SS:[EBP-C] ; |

00431346 |. 52 PUSH EDX ; |Arg1 0012F34E, Buffer, 紧接"Debug string: "

00431347 |. E8 C0FF0200 CALL OLLYDBG._Readmemory ; /_Readmemory( 调用 ReadProcessMemory )

0043134C |. 83C4 10 ADD ESP,10

0043134F |. 3BC3 CMP EAX,EBX ; EAX 真正读到的字节数

00431351 |. 74 0A JE SHORT OLLYDBG.0043135D

00431353 |. B8 01000000 MOV EAX,1

00431358 |. E9 C8000000 JMP OLLYDBG.00431425 ; 读到的字节数不对, 结束

0043135D |> 015D F4 ADD DWORD PTR SS:[EBP-C],EBX ; "Debug string: "+ debug_string 的长度

00431360 |> 8B55 F4 MOV EDX,DWORD PTR SS:[EBP-C]

00431363 |. 8D8415 97FDFF>LEA EAX,DWORD PTR SS:[EBP+EDX-269] ; 指向字符串末尾

0043136A |. EB 04 JMP SHORT OLLYDBG.00431370 ; 去掉未尾的 0, 0D, 0A

0043136C |> FF4D F4 /DEC DWORD PTR SS:[EBP-C]

0043136F |. 48 |DEC EAX

00431370 |> 8038 00 CMP BYTE PTR DS:[EAX],0

00431373 |.^ 74 F7 |JE SHORT OLLYDBG.0043136C

00431375 |. 33D2 |XOR EDX,EDX

00431377 |. 8A10 |MOV DL,BYTE PTR DS:[EAX]

00431379 |. 83FA 0D |CMP EDX,0D

0043137C |.^ 74 EE |JE SHORT OLLYDBG.0043136C

0043137E |. 33C9 |XOR ECX,ECX

00431380 |. 8A08 |MOV CL,BYTE PTR DS:[EAX]

00431382 |. 83F9 0A |CMP ECX,0A

00431385 |.^ 74 E5 /JE SHORT OLLYDBG.0043136C

00431387 |. 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C] ; 修正后的长度

0043138A |. 8D95 98FDFFFF LEA EDX,DWORD PTR SS:[EBP-268] ; 字符串指针

00431390 |. 85FF TEST EDI,EDI

00431392 |. C68405 98FDFF>MOV BYTE PTR SS:[EBP+EAX-268],0 ; 修正后字符串末尾补零

0043139A |. 52 PUSH EDX

0043139B |. 75 04 JNZ SHORT OLLYDBG.004313A1

0043139D |. 33C9 XOR ECX,ECX

0043139F |. EB 03 JMP SHORT OLLYDBG.004313A4

004313A1 |> 8B4F 2C MOV ECX,DWORD PTR DS:[EDI+2C]

004313A4 |> 51 PUSH ECX ; |Arg1

004313A5 |. E8 56CDFFFF CALL OLLYDBG.0042E100 ; /OLLYDBG.0042E100, F7 跟进

004313AA |. 83C4 08 ADD ESP,8

0042E100 /$ 55 PUSH EBP

0042E101 |. 8BEC MOV EBP,ESP

0042E103 |. 81C4 04F0FFFF ADD ESP,-0FFC

0042E109 |. 50 PUSH EAX

0042E10A |. 81C4 FCFDFFFF ADD ESP,-204

0042E110 |. 53 PUSH EBX

0042E111 |. 56 PUSH ESI

0042E112 |. 57 PUSH EDI

0042E113 |. 8D5D 10 LEA EBX,DWORD PTR SS:[EBP+10]

0042E116 |. 837D 0C 00 CMP DWORD PTR SS:[EBP+C],0 ; 字符指针是否为 0

0042E11A |. 74 3D JE SHORT OLLYDBG.0042E159

0042E11C |. 6A 00 PUSH 0 ; /Arg1 00000000

0042E11E |. E8 59340000 CALL OLLYDBG.0043157C ; /OLLYDBG.0043157C

0042E123 |. 59 POP ECX

0042E124 |. 53 PUSH EBX ; /Arg3 0012DA9C

0042E125 |. 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C] ; |

0042E128 |. 50 PUSH EAX ; |Arg2 0012F340 ASCII "Debug string: %s%s%s"

0042E129 |. 8D95 FCEDFFFF LEA EDX,DWORD PTR SS:[EBP-1204] ; |

0042E12F |. 52 PUSH EDX ; |Arg1 0012C888

0042E130 |. E8 1F8B0700 CALL OLLYDBG.004A6C54 ; /int vsprintf( char *buffer, const char *format, va_list argptr );

这时如果 F8, OllyDbg 在 4a74cf 出现访问 [00000001]异常, 看一下 Stack, 很容易知道原因

0012DA9C |03A9066C

0012DAA0 |004B59E6 ASCII "%s - %s"

0012DAA4 |00000001

argptr12DA9C

第1个 %s 就是 03A9066C 指向的字符串, [03A9066C]0

第2个 %s 就是 004B59E6 指向的字符串 "%s - %s" (这完全是巧合)

第3个 %s 就是 00000001 指向的字符串, 哈

如果 是 Test1.eXe , 只有1个%s, 运气非常好, 能走过去, 12C888 变成 "Debug string: ", 以后也不会有问题了. OK

如果 是 Test2.exe , 只有2个%s, 暂时运气不错, 能走过去, 12C888 变成 "Debug string: %s - %s"

继续走

0042E21B |. 50 PUSH EAX ; /Arg3 0012C888 ASCII "Debug string: %s - %s"

0042E21C |. 6A 00 PUSH 0 ; |Arg2 00000000

0042E21E |. 8B55 08 MOV EDX,DWORD PTR SS:[EBP+8] ; |

0042E221 |. 52 PUSH EDX ; |Arg1 7C59BBF3

0042E222 |. E8 E5C30200 CALL OLLYDBG._Addtolist ; /int __cdecl Addtolist(int,char,char *format,int arglist), F7 跟进

0042E227 |. 83C4 0C ADD ESP,0C

0045A60C >/$ 55 PUSH EBP

0045A60D |. 8BEC MOV EBP,ESP

0045A60F |. 81C4 F8FDFFFF ADD ESP,-208

0045A615 |. 53 PUSH EBX

0045A616 |. 56 PUSH ESI

0045A617 |. 837D 10 00 CMP DWORD PTR SS:[EBP+10],0

0045A61B |. 0F84 0E020000 JE OLLYDBG.0045A82F

0045A621 |. 8D45 14 LEA EAX,DWORD PTR SS:[EBP+14]

0045A624 |. 50 PUSH EAX ; /Arg3 0012C87C

0045A628 |. 52 PUSH EDX ; |Arg2 0012C888 ASCII "Debug string: %s - %s"

0045A62F |. 51 PUSH ECX ; |Arg1 0012C660

0045A630 |. E8 1FC60400 CALL OLLYDBG.004A6C54 ; /int vsprintf( char *buffer, const char *format, va_list argptr );

这时如果 F8, OllyDbg 在 4a74cf 出现访问 [00000260]异常, 看一下 Stack, 很容易知道原因

0012C87C |03A902EC

0012C880 |00000260

argptr12C87C

第1个 %s 就是 03A902EC 指向的字符串, [03A902EC]0

第2个 %s 就是 00000260 指向的字符串,

4 结论

从上面可知 OutputDebugString("%s%s%s") 对付 OD 的原理, 修改 OD 方法也很简单.

431360-431385 这里没什么用, 可以修改成下面

00431360 |> /8B55 F4 MOV EDX,DWORD PTR SS:[EBP-C]

00431363 8D85 98FDFFFF LEA EAX,DWORD PTR SS:[EBP-268]

00431369 8038 25 CMP BYTE PTR DS:[EAX],25 ; 找 "%"

0043136C 75 03 JNZ SHORT OLLYDBG.00431371

0043136E C600 26 MOV BYTE PTR DS:[EAX],26 ; 用 "&" 代替

00431371 40 INC EAX

00431372 4A DEC EDX

00431373 ^ 75 F4 JNZ SHORT OLLYDBG.00431369

00431375 90 NOP

00431376 90 NOP

00431377 90 NOP

00431378 90 NOP

00431379 90 NOP

0043137A 90 NOP

0043137B 90 NOP

0043137C 90 NOP

0043137D 90 NOP

0043137E 90 NOP

0043137F 90 NOP

00431380 90 NOP

00431381 90 NOP

00431382 90 NOP

00431383 90 NOP

00431384 90 NOP

00431385 90 NOP

00431386 90 NOP


第一篇 认识ODbgScript

第四卷 OllyDBG 插件系列 第一篇 认识ODbgScript

作者：hnhuqiong

看到CCDebuger的图文并茂的OD教学系列很受欢迎,这里我也仿照它的教学模式写吧,这样容易让新手学习,CCDebuger不会让律师给我发版权函吧 :),我感觉看雪如果形成了自己的教学风格也是不错的.

一、ODbgScript 的介绍

调试程序,程序逆向工作,加密解密都存在大量的重复工作,ODbgScript就是一个让你从重复而又枯燥的劳动中解脱的一个工具.OllyScript是OLLYDBG的一个功能增强插件(Plugin),OllyScript使用的脚本语言是一个种类汇编的语言。你使用它来控制OLLYDBG和进行脚本运算.

它的内部机制是一种解释执行的方式,当然,大侠luocong开发了一个和这个功能相近的插件OM,所不同的是OM是运行在虚拟机上的,而ODbgScript是解释执行.现在ODbgScript在世界上有大量的使用者和他们发布了大量的脚本,你能好好的利用它.

ODBGScript的前身是由SHaG开发的OLLYScript,感谢SHaG开发了这么好的插件,并且开放了它的源码,SHaG于2004年停止了它的开发,直到今天,还有众多的爱好者在使用它.从2005-11-4开始由Epsylon3继承SHaG继续开发并变更插件名就是你现在看到的ODbgScript,并且继续开发源码发行.它现在的最后版本是1.47。

由于Epsylon3开发的版本最大的变化是出现了调试运行窗口.

hnhuqiong在ODbgScript开放源码的基础上对其进行了本地化,并且优化(BugFix).如果经原作者同意,愿意将此插件继续升级.

天工开物,必物尽其用....

二、ODbgScript 的安装

ODbgScript 1.48 版的发布版本是个 RAR 压缩包，要解压到OD的plugin目录下，运行OD就可以在插件栏看见ODbgScript插件了.点击脚本运行窗口就可以看见新弹出一个脚本运行窗口( 老的OLLYscript0.92版本没有运行调试窗口.)

ODbgScript中插件窗口的功能如上图。

简单解释一下各个选项的功能，更详细的内容可以看readme.txt中文帮助：

运行脚本：直接调入脚本运行,网络上有大量的各种脱壳和解密脚本文件,你可以在这里直接引入运行。

中止：中止脚本运行。

暂停：暂停脚本的运行。

继续：脚本处于暂停状态下,可以恢复脚本继续运行

单步：单步执行脚本每一句。

脚本运行窗口:这个是新版ODbgScript增加最为有用的一个功能,可以查看脚本的运行状态,观察脚本变量的结果,以及控制脚本的运行.上面图片中,就是ODbgScript的脚本运行窗口的模样.

脚本日志窗口:这个模块还不完善,在以后将逐渐完善它.

关于:这里显示的DbgScript的版权信息.

二、 ODbgScript的脚本调试窗口

(1)脚本调试窗口的介绍:

行号栏:引入脚本后,ODbgScript将脚本复制入这个窗口,并为每一句脚本语言赋予一个行号

命令栏:就是脚本命令显示在这里

返回值栏:就是函数返回值$Result显示在这里,$Result是ODbgScript的一个保留变量名,它一般保存各脚本命令的返回结果.

EIP栏:就是脚本命令现在位置,所控制的OD的EIP值,这里是观察脚本控制OD流程的地方.

计算结果栏:ODbgScript可以让用户使用变量,这里就是显示变量的计算结果

(2)ODbgScript的右键菜单功能

好了现在我们在ODbgScript运行窗口点击右键,将弹出一个菜单.如图

运行脚本:和插件菜单功能相同,直接调入脚本运行,网络上有大量的各种脱壳和解密脚本文件,你可以在这里直接引入运行。

脚本断点切换(F2):对光标处脚本的命令下一个断点标记,重复一次就是取消断点标记.这个功能的快捷键是F2

光标处下断点并执行(F4):对光标处脚本的命令下一个断点标记,并让脚本运行到这里.这个功能的快捷键是F4.

单步执行(S):单步执行脚本的语句.这个功能的快捷键是S键

继续(SPC空格键):如果脚本处于暂停状态,切换脚本到继续运行状态,这个功能快捷键是 空格键(SPC)

放弃(ESC):放弃脚本运行状态,也就是让脚本复位,重新处于第一行的位置.

编辑变量:ODbgScript有一个功能很好用,就是变量的使用.这里是查看变量和编辑变量的地方.

如果进入变量,可以看见2个值,一个是0x表示的是16进制值,一个是.表示的是十进制值

手工执行脚本命令:有时候调试脚本的时候,需要手工运行脚本命令,那么这里就是为你提供这个功能的地方.

其它介绍:如果引入了脚本,你会看见一个横条,这个是标号分界条,它利用脚本标号对脚本分界.

让你的脚本可读性大大加强,建议今后的脚本可以多使用标号.

光标条:脚本运行到的脚本命令位置会有一个光标条,当然它的颜色可以根据你的要求来调整.

好了,从基本上ODbgScript介绍完了,我们来看看它在OD中和其它窗口联合的工作状态.

和ODbgScript配套使用的程序有loveBoom开发的Ollyscript editor 1.0b3.

如图:

它是一个ODbgScript语言编辑器.

好了,首篇先到这里,下一篇将介绍ODbgScript的语言和命令.


第二篇 ODbgScript的脚本命令(1)

第四卷 OllyDBG 插件系列 第二篇 ODbgScript的脚本命令(1)

作者：hnhuqiong

总观ODbgScript洋洋洒洒那么多脚本命令,其实它分成了6大类:

1.变量和注释类和杂类 2.断点类 3.系统和操作类 4.流程控制类 5.计算和寄存器操作类

6.字符串操作和搜索和替换类

这里我就不那么具体的举例子了,因为那么多命令要是逐一图片讲解将成为天书了.

而具体的例子将在后面的章节来论述.这里将论述的是思想.授人以渔是上策!

文章不短,建议边听音乐边看(建议曲目:喜唰唰,披着羊皮的狼)

一. 变量和注释以及杂类

$RESULT :这个是系统保留变量,函数和ODS的命令返回都放这里.它还有一个隐含的变量为$RESULT_1,有的返回是2个参数,第二个放这里.

$VERSION :返回当前版本号

VAR :在脚本中，声明一个变量

AN :从指定处，对代码进行分析

CMT :在指定地址处，加入注释

MSG :将指定消息，显示到一个对话框中

MSGYN :将指定消息，显示到一个对话框中，这个对话框有“是”、“否”按钮

ASK :显示一个提示输入框，让用户输入，并将结果保存转保留变量$RESULT中。（如果用户按了取消键，则$RESULT0）

#INC :脚本里面包含了另外的脚本

这是一个小类,它们主要就是做变量和注释工作.其中唯一需要注意的是var命令,它是一个变量声明命令,如果要养成一个好的脚本编辑习惯,你最好将脚本的变量名字命令的通俗易懂,这样,不会到了时间久远而不至于你自己都看不懂你的脚本.

例子:

var a :这就不是一个好的变量名,其它人或者时间久了你都不知道它是干什么用的了.

好的例子--var entrypoint

二.断点类

------普通断点类

BP :在指定地址设断点

BC :清除指定地址的断点

------条件断点类

BPCND :在指定地址处，设置条件断点

BPL :在指定地址处设置记录断点，将表达式的结果记录到记录窗口中

BPLCND :在指定地址处设置记录断点，如果条件为真时，将表达式的结果记录到记录窗口中

------硬件断点类

BPHWS :在指定地址，设置硬件断点

BPHWC :删除指定地址处的硬件断点

BPHWCALL :删除所以的硬件断点

------内存断点类

BPRM :在指定地址处，设置一个内存读取断点

BPWM :在指定地址处，设置一个内存读写断点

BPMC :清除内存断点

对于调试和逆向工作来说,断点是重中之重!只有掌握了断点基本技能,才能说你学会了逆向工作的入门.

有过编程经验的人都知道在源代码下调试还是比较容易的,因为你可以在源代码上直接下断点调试.而逆向就不同了,你面对的是二进制文件,如何找断点,怎么下断点都是靠常年累月积累下来的经验.当然在看雪你能学到很多很多的下断点技巧.ODbgScript里面提供的断点基本囊括了OD强大的下断点的功能.而如何灵活的组合和使用它们将让你的逆向水平有质的飞跃.

这一节可以和CCdebuger的OD断点文章相参照学习脚本的编辑和断点使用.

ODbgScript断点命令是通过OD的API接口向OD下断点命令的,所以,只要你在OD中能下的断点,那么,在脚本中就能一样下同样的断点.所不同的是一个是你手工下,一个是脚本帮你下!可以这样说,逆向和解密就是不断的靠近自己所需要的核心地带,只要真正的找到了核心地带,那么剩下的就是程序等待你的肢解和解剖了.而这个过程就是如何利用断点来靠近它.而让人痛苦的是,常常你要经过无数次的实验和验证你才会靠近你所需要的核心地带.没有ODbgScript以前,这个是一个让人痛苦不堪的过程,特别是如果程序有大量的干扰和anti,你必须手工小心翼翼的越过这些障碍,爬山涉水千艰万险的来到程序某个地方,一不留神被ANTI打倒,你必须重头再来,这个过程任何玩逆向的人都深有体会.特别是在没有OD以前,记得多年前我在2.8版的SICE上,为记录很多东西不得不鬼画桃符的写的到处都是记录.而一旦"失手",那么很多过程是不得不再做一遍.其中的艰辛不是能描述的.

而有了ODbgScript你就不同了,anti也好干扰也好,它能让你过去的工作在瞬间帮你完成.你就能集中力量寻找你的核心地带,而不会被已经分析过的各种干扰,anti所打倒!它就是我们对付ANTI和虫海战术的利器!!!可以说一个软件或者一个壳无论你有多复杂,无论你多虫海,只要有一个人有真正进展!并公布工作脚本,尽管是半成品那么其它人就可以利用脚本不断的站在前人的肩膀上,向你进攻!壳是相对静态的防守,这就是加密和加壳软件的悲哀!你是anti是利用不为人知的后门,就有无数人想方设法靠近你的命门,那么你的"专利"就大白天下了.你是多么复杂的算法,多么变态的VM,那么只要每天有人在算法上进步一点点,点点滴滴的积累将会铁棒磨成针,不要小看逆向界人的耐心!!!!

由于后门和有效ANTI越来越少,加密和加壳开始向虫海方向发展,也就是利用反复的加密算法和动态解码,以及VM(虚拟机)来产生大量的垃圾代码,反复和你斗你的耐心,从个人角度来说,的确逆向界开始遇到了一堵厚实的墙.但大家要相信一句话,自古有矛就有盾!!!.我还相信一句就是众志成城!!!这也是我想发展ODbgScript的初衷和下力气写这篇文章的动力.

罗嗦了半天,我自己都感觉我像缠脚的老太婆了.

其实我们用ODbgScript的一个主要目的就是-----直奔主题!

好了,我现在也回来直奔主题.我说到哪里里了??(各种砖头都来了)

这里,我们来演示一下断点的设置,看看在ODbgScript中下一个断点,那么OLLYDBG中有什么反应???

我们调出你的编辑器,先写一个简单的脚本如下,然后保存成txt或者osc文件:

bp 40a910

bc 40a910

然后调出脚本运行窗口,引导你刚才写的脚本进来,按下S键,我们看看ODbgScript和OD中的断点窗口各有什么反应.

脚本窗口里面一按S,马上OD的断点窗口就产生了一个断点.再按一下S,OD中的断点窗口中的断点又消失了.

哈哈,我再按,我再按.哟,怎么没有反应了?当然就2句命令,你按那么多抽风呀???

(被一顿暴打,教学就教学,哪来那么多怪话)

那么,现在我们知道了,ODbgScript和OD之间通过 Plug-in API接口能很好的沟通.最起码比OD断点窗口里面你要手工设断点激活或者删除强多了吧?要知道偷懒的人多了去了(被无数人怒目而视,寒~~~~),能省下时间干什么去??直奔主题!!!!每次开会看见我们单位的头我都在假想,他会逆向多好呀....

OD中断点主要分为4小类,普通断点,内存断点,硬件断点,条件断点.我不打算讲解断点的原理,有兴趣大家可以看看雪大量的文章,大侠级别的,断点玩的都不叫断点叫INT,这个是什么?我不知道,我是菜鸟级别,不敢往这个上面发言....

那么,这四小类断点和其它命令的的组合将带你进入五光十色的逆向世界!!!

3.系统和操作类

-----内存操作类

ALLOC :分配新的内存页, 你能读／写／执行.

FREE :释放由ALLOC申请的内存.

-----文件操作类

DM :从指定地址处开始，在内存中提取指定大小的数据，并保存到指定的文件中

DMA :从指定地址处开始，在内存中提取指定大小的数据，并保存到指定的文件中；

如果指定文件已存在，则将数据追加到指定文件尾部。

DPE :提取执行模块到指定文件中。

WRT :Write to file (replace existing one) the only accepted symbol is

"/r/n"Numbers are wrote as strings... for the moment

WRTA :Append to file

LM :引导Dm文件到内存 ,LM is the opposite of the DM command

-----系统信息类

GPA :在指定的动态链接库中，获得指定函数的地址。

GCMT :获得指定地址的注释

GMEMI :获得内存块的指定信息

GPI :获得进程信息

GN :获得指定地址的符号名（比如指向API函数）

GMI :获得指定地址所在模块的相关信息。

HANDLE :获得句柄信息

-----汇编和OD控制类

ASM :修改指定地址的指令。

EXEC/ENDE :对当前调试进程，执行在EXEC和ENDE之间的指令

KEY :相对于在OD中执行按键

古话有说:满纸荒唐言,一把辛酸泪.

其实,这段是最难写的,因为这段是承上启下的一段,有了这段你才能体会ODbgScript的强大,才能从这里开始和中断组合产生各种五光十色的脚本世界,来到这段我常常有一种如履薄冰的感觉,生怕误人子弟.这里别看命令不多,可句句都是重点.我在看源程序的时候,对这段的了解才更加的加深了我对OD和ODbgScript的理解.

而ODbgScript今后如果能成功升级,核心的重点也在这里.

古人云:工欲善其事,必先利其器.如果说中断是ODbgScript的器.那么这里就是利你的器!!!是情报和特工及军队机构,没有情报机构,那就是瞎打乱打.没有特工机构,就会让你身处危险而不知其险.没有军队,你面对敌人强大的防护,束手无策!

希望不至于写成类MSDN的东西,可很多东西又难以取舍,霍金有一句话,你的一个公式能让你的读者少50%.我尽量能写成入门级的东西,霍金的名言才让他的《时间简史》让无比抽象的时空概念深入人心,让千千万万的老百姓明白什么是黑洞和黑洞辐射.

好了我们再次奔小康,不是不是,是奔主题!!!!(场外人全部处于抓狂中....)

首先我们在脚本里面写下这样一些命令,引入ODbgScript然后按S执行.

gpa "MessageBoxA", "user32.dll"

bp $RESULT

esto

bc $RESULT

大家看到了,在返回值栏里面有一个值,这个值是什么呢?就是函数MessageBoxA的入口地址,这个函数常常被注册码类的程序所调用,给大家一个比如注册码错误之类的提示.如果我们要定位这个注册码的算法位置,那么如果弹出来了这样的窗口,算法就在附近不远的地方.情报机构上!!!!告诉我坐标,中断大炮在后面等着.大炮要打敌人第一件事情是要知道敌人在什么地方.而这个MessageBoxA的入口地址就是敌人首先暴露的一个坐标点.我们利用GPA命令来定位它的位置,而这个位置是随着计算机的不同而不同的,我们要抓住敌人,就要充分利用GPS来定位它!不对不对,不是GPS是GPA!!!(场下开始骚乱,台上死猪不怕开水烫的继续说),情报机构准确的抓到了这个位置,那么通信兵是谁???就是$Result!!它越过重重封锁线将坐标地址给中断大炮阵地送来了坐标,预备!~~~放!!!我们按下空格键让中断大炮去打吧.

轰!程序诧然停止.打中啦~~士兵狂吼,硝烟散去.

作为指挥官的你邹了一下眉头,自言自语的说,是打中了,可离人家指挥中心差老鼻子了.又不是原子弹,覆盖范围可以那么大!

怎么办?还需要更加精确的坐标!我们要派特别行动队出动了.

脚本上开始加下面的句子,然后执行,并调出OD的内存窗口,可以进行观察:

alloc 2000

我们可以看到OD的内存区中增加了一个块,大小2000属性RWE.大家对比一下图3和图4

(我们在敌人的眼皮低下打了一个埋伏区..这个埋伏区可以藏弹药,可以藏军队,还可以打一场CS............

....台上不说话了,CS激战中......想暗渡陈仓?小心点记得留退路哟...

突然听见MS中央电台广播在不断的播放歌曲:

"洗刷刷,洗刷刷,请你--拿了给我的送回来,吃了我的给我吐出来,欠了我的给我补回来,偷了我的给我交出来!!!"

侦测连长看偷袭阵地这样长期占领危险,还是有借有还!再来不难,马上烧军火等等走人.)

free $RESULT,2000

这里指挥官还派出了外交官,要打击敌人就要从多方面的入手.

gmi eip,CODEBASE (或者CODESIZE,或者 MODULEBASE,MODULEDSIZE等,外交部在不断扩大范围中...)

某某情报局派出了天下无敌光头武乞丐功夫王"零零漆"----外号"走醒醒"上场.

GPI MAINTHREADID(可以是 HPROCESS,PROCESSID,HMAINTHREAD,MAINTHREADID,MAINBASE,PROCESSNAME......)

某新闻社派出了强大的以"不是零"为代表的著名文字分析专家记者代表团.要知道记者是无冕之王!!!!!!

这里可以说你会明白为什么"焦点访谈"为什么那么可怕了.老百姓欢迎,有的人就要....

(这里放到下面的一章专门讲叙)

为了打好这场战役,某野战军派出了王牌之师,ASM给以策应.

ASM eip,"mov [eax],edx"

从战役角度来说,围点一定要打援,那么,现在敌人那么狡猾,我们怎么能缺少这样的策应队伍呢???

它为什么是策应队伍呢?因为它只能向代码区汇编一句,效率比较低.但是毕竟是在和敌人干上了...

最高首长派出了空军王牌师EXEC/ENDE,直接轰炸给予空中支持,可惜空军轰炸机只有能装255颗炸弹的弹药仓.

exec

mov eip,[eax]

ENDE

对当前调试进程，执行在EXEC和ENDE之间的指令。有这个命令就是你可以越过ODbgScript直接跳入进程,对进程进行直接控制.它的原理就是取当前进程的信息进行保存,然后新分配一个代码内存区(可读/写/执行.大小1000)调用OD汇编器将你的汇编语句转成OPcode,将OPcode拷贝到代码区,然后将EIP指向你的代码开头.然后将控制权交给你.执行完后将EIP归还原位,然后将控制权交还ODbgScript.这里的好处就是让你以很高的效率来避免在较慢的脚本环境运行需要高效的操作.

!注意:由于进程控制权交给你了,那么,你的代码有效性将只有你自己来控制了.

!注意:执行后不保存现场.这都需要你来做工作.(要保存现场,你可以使用pushad,popad)

有大括号的，会被大括号中的变量的值替代。

(这对命令有小BUG在下一个发布版本中修正)

还有就是我们英雄的幕后部队,电子对抗队伍KEY在默默的支持着这场生死之战..

key 20, 1 //Shift+space

一场战役就这样开始打响,轰轰烈烈.................(世界没有免费的午餐)

各种部队和敌人短兵相接....刺刀见红..............(见后章)

你会看见我们的军队是那么连贯的一气呵成完成了使命..(效果出来了)

千帆过尽谱写的是华丽篇章........................(过程是那么美不盛收)

尽管尽管曾经历经千山万水........................(过程是那么令人辛酸)

可最终还是剑指七寸红颜笑........................(原来是披着羊皮的狼)

最后是你哼着"日落西山打靶归...."

打扫战场,收获你的战利品,文件类操作就是干这个的.......

dpe "c:/unpack.exe", 401000

(再不睡觉我LP要和我刺刀见红了 :( )

好了,这篇先到这里,下一篇将介绍ODbgScript的文字串命令和流程控制命令.


第三篇 ODbgScript的脚本命令(2)

第四卷 OllyDBG 插件系列 第三篇 ODbgScript的脚本命令(2)

作 者: hnhuqiong

4.流程控制类

我曾经教过一个小DD写脚本,他老是把OD的运行流程和ODbgScript的流程搞混淆了,这个概念要弄清除,作为新手常常弄不清这两个概念,而这两个概念是必须要弄明白的!要知道ODbgScript是一个脚本插件,它执行脚本的流程,靠脚本运行过程中各种返回值/计算值判断后,修改/控制/影响OD的流程.

还是打个比方吧:

ODbgScript是铁路传感/信号/道岔控制系统,OD就是列车,而各种返回值就是各种信号灯和传感器,列车的运行过程中,传感器系统不断的给出列车的各种运行状态,而ODbgScript就根据这些运行状态来调相应的运行时刻表让列车在控制的范围内运行.那么ODbgScript控制道岔系统就完成了一趟完整的调度系统. 大家都坐过火车吧??你喜欢的是打着信号灯的铁道工人?还是现代化的排灯/道岔控制系统???

对应上一章的中断系统,我们又换概念打比方了(呵呵,对于新手我还是感觉自己有自己的一套办法来教大家弄清这些的),断点在这里就是在铁轨上的信号灯,传感器它感受到了列车(OD)到这里了,马上给控制系统(ODbgScript)发回信号,控制系统知道列车在什么位置后,相应的控制---信号灯/道岔系统,列车就乖乖的听从我们的调度来运行了.脚本其实就是列车时刻表!!!而脚本内的标号就是所管理的机务段!

邓爷爷说:实现四个现代化,靠的是科技!

hnhuqiong说:OD逆向实现自动化,靠的是ODbgScript! (被愤怒的群众痛殴中!!!)

-----ODbgScript流程控制类

EOB :在下次中断发生时，跳转到指定标签处

COB :发生中断后，让脚本继续执行（移除EOB指令）

EOE :在下次异常发生时，跳转到指定标签处

COE :发生异常后，让脚本继续执行（移除EOE指令）

JA :大于则跳到指定标签处，在cmp命令后使用

JAE :大于等于则跳到指定标签处

JB :小于则跳到指定标签处，在cmp命令后使用

JBE :小于等于则跳到指定标签处

JE :等于则跳到指定标签处

JNE :不等于则跳到指定标签处

JMP :跳转到指定标签.

PAUSE :暂停脚本运行

RET :退出脚本

-----OD流程控制类

RUN :相当于在OllyDbg中按 F9

GO :执行到指定地址处 F4

ESTI :相当于在OllyDbg按 SHIFT-F7

ESTO :相当于在OllyDbg按 SHIFT-F9

STI :单步步入,相当于在OllyDbg中按 F7

STO :单步步过,相当于在OllyDbg中按 F8

RTR :相当于在OllyDbg中执行 "Run to return"操作 [Ctrl+F9]

RTU :相当于在OllyDbg中执行 "Run to user code"操作 [Alt+F9]

AI :在OllyDbg中执行“自动步入” [Animate into]操作 [Ctrl+F7]

AO :在OllyDbg中执行“自动步过” [Animate over]操作 [Ctrl+F8]

TI :相当于在OllyDbg中执行 "Trace into" 操作 [Ctrl+F11]

TO :相当于在OllyDbg中执行 "Trace over" 操作 [Ctrl+F12]

TICND :执行 "Trace into" 操作，直到条件为真时停止

TOCND :执行 "Trace over" 操作，直到条件为真时停止

TC :关闭Trace

LBL :在指定地址处插入一个标签

现在大家明白了ODbgScript可以给我们带来多少便利了吧????概念恐怕大家已清楚了,就不多讲解了.

其实,这些都是自动控制专业范围内的东西,刚好我本科是这个专业,所以比较轻车熟路. :)

现代化社会这些自动控制无所不在,只是你用上了却不一定留心它!

用一个复杂一点的脚本示意图我们简单的来进行类比,让大家牢牢记住它的(感谢FLY的FSG V1.X-2.X脚本)

5.计算和寄存器操作类

有了刚才的概念,那么这里的计算概念就好办了,这些都是ODbgScript内的计算.这里就不多讲解了.就是现在命令有点少,我争取在下一个版本加多命令,这样调度起来不至于不会计算了,只好上铁轨手工操作了..

INC :对变量进行加一操作

ADD :源操作数与目的操作数相加，并把相加的结果保存到目的操作数中

DEC :对变量进行减一操作

SUB :和汇编里的sub一样，两个数相减

AND :源操作数与目的操作数进行逻辑与操作，并将结果保存到到目的操作数中

XOR :源操作数与目的操作数进行异或操作，并将结果保存到到目的操作数中

OR :源操作数和目的操作数做逻辑或操作，并将结果保存到到目的操作数中

SHL :左移目的操作数，n比特位；并将结果保存到到目的操作数中

SHR :右移目的操作数,n 比特位；并将结果保存到到目的操作数中

CMP :比较 目的操作数与源操作数的大小，和其对应的汇编指令作用相同, 现支持字符串比较

REV :字节反转.

MOV :将源操作数移动到目的操作数中

6.字符串操作类

LEN :获得字符串长度

FIND :从指定地址开始在内存中查找指定的内容

FINDOP :从指定地址开始查找指定一个指令，这个指令是以指定内容为开始的

FINDMEM :全内存中查找指定的内容

ATOI :转换一个字符到整数，返回结果保存到保留变量$RESULT中

ITOA :字符串进制转换

EVAL :计算含义变量的表达式

EXEC/ENDE :对当前调试进程，执行在EXEC和ENDE之间的指令

FILL :从指定地址处，填充指定长度的值

REPL :在指定地址开始，在指定长度字节内，用“替换字符串”替换“查找字符串”

SCMP :比较字符串,目的和源比

SCMPI :比较字符串,只不过是源和目的比

PREOP :获得当前地址的汇编指令

REF :交叉参考

OPCODE :反汇编当前指令

这里是一个很强技术概念类的类,为什么这样说呢?因为完全靠系统信息来定位断点是远远不够的,那么我们必须寻找程序内的"特征代码串",这个和杀病毒软件是一个概念,杀病毒软件是怎么干活的??就是搜索内存,找各个程序的可疑的"特征字符串或者特征码".和人的DNA一样,各个程序的DNA是不同的但又有相同的地方这就是亲子鉴定中心赚钱的秘方 :).

从逆向角度来说,某个系列的加壳,加密软件它有它的一贯性,因为它的开发架构已经定死了,除非它大规模推倒从来.这里我就自己创造一个叫法叫"程序的DNA"吧,这个不是我提出来的,很多大侠早就提出了这个概念,比如FLY的"特定码",OPcode其实就那么多,但是就是这些,简简单单的OPcode就组成了千千万万的程序,但有些东西是具有一贯性的,这个和生物学上的遗传概念一模一样.药剂学上有一个东西叫生物试剂,就是利用生物的特定DNA来检测的.

一旦我们找到某个"程序的DNA",那么很多东西就简单多了,大侠级别的就是"程序DNA试剂工厂",他们能够通过他们长年累月的积累总结出了大量的"程序DNA特征"检测手段,并且能够敏感的捕捉到新的"程序DNA特征",这些才是逆向水平高低的所在.

为什么呢会这样呢?因为程序必须某时某刻要干某件事,这就注定了它的死结,大侠们能一眼就看明白这段OPcode它是在干什么(这需要长年累月的积累),你要动态解码,那么你解码的一小节就是你的命门,你要ANTi,你anti前必须做准备工作,这个准备工作就是命门等等诸如此类.你要干什么,我就在你要干什么前定位你的"特征码",在你干什么前把你的手段给残废了(大侠们够残忍了吧?呜~~~我的ANTI,我的解码)

这就是矛和盾永远可以斗争的地方,我anti你,你定位我的ANTI,好,那么我要动态解码后才ANTI,大侠就盯上你的动态解码的回写内存,我把我动态解码一段花了,那么,我用花特征分离出你的花.......

..逆向反ANTI的手段就是在你动手前我先动手!!!!这叫先发制人,一招快,招招快!!!!可怜程序它必须按照列车时刻表来跑,最搞笑的是脱壳领域,有的大侠懒得理哪些大规模的ANTi了不费哪个劲脱你了,就干脆写个小东西Loader带跑.穿透壳后用特征码一步步定位后patch掉,何必处处脱呢........

这里,我说了一通,大家可以看图一,第20行命令find eip,#fe??0f84#, 这个句话的含义就是寻找特征字符串fe??0f84,而这个特征字符串是什么含义呢,??代表的是可以是Hex任何数, 说明被2.0版FSG保护程序在第一次运行系统函数LoadlibraryA后,从这里取出ESP指针所指的内存地址,下断点后,就会出现了这个特征字符码.这个是2.0版特有的.大侠们发现了它的这个特征码.它出一定会出现在被FSG2.0保护程序经过上面运行步骤后的某个内存地址中.

大家是不是有点豁然开朗的感觉??

我们来分析一下轰动一时的特征串:

00B2C27C 68 A78FC31F PUSH 1FC38FA7

f9，改eip

00B7266A CALL 00B7267C

68A78FC31F就是在程序还没有启动运行的时候,就存在的一个特征字符串,它存在于加载到内存后的地址00b2c27c上,大侠发现了它的这个BUG,就是在00b2c27c处,直接将列车扳道岔到00b7266a上,火车一下子变轨到b7266a处,从而跳过了themida的验证.

我们用脚本来实现这个思想是什么样的?

var bugaddress1

var bugaddress2

find eip,#68A78FC31F# //查找"特征字符串"

mov bugadr1,$RESULT //保存查找到的地址

bphws bugaddress1,"x" //给bugaddress1处信号灯传感器系统下命令(断点)

run //列车放行,会被bugaddress1处的信号系统命令停下来

bchwc bugaddress1 //信号灯系统复位,但列车还是停在原位等待放行 :)

mov bugaddress2,b7266a //将b7266a的地址给变量

mov eip,bugaddress2 //扳轨改变列车时刻表,从bugaddress1-bugaddress2处的

//所有站点都不过了,直接走近道到bugaddress2.

//这下这些站点的"乘客"们傻眼了,被脚本指挥的列车拒载了.

esto //列车放行

这个特征字符串仅仅存在于themida的1.3.3.0中,而不是全系列的themida的特征字符串,但是这个思想只要有了就容易了,可你是否具备了一眼就能从全系列themida中利用它的这个思想来定位它的"特征字符串"的能力,那就需要你不断的加强你的功力了.其实1.3.3.0爆出这个BUG,各路大侠马上行动,先从1.3.3.0版本中定位b2c27c和00b7266a地址处上下文中有什么可以进行再定位的地方,其实这就是这个系列的DNA指纹.从而一代枭雄轰然倒地!而上面的脚本的bugaddress1,和bugaddress2就是大侠们利用ODbgScript来进行反复搜索的利器.

(习题,你能从1.3.3.0版本里面找到通用于1.3.3.5版bugaddress1的DNA指纹么?)

这里我们讲解完了,ODbgScript的命令等等,下面章节我将讲解ODbgScript的使用注意事项和使用技巧.

(作为脚本创作大户,原loveboom,fly都是脚本高手,其实他们才是真正的ODbgScript的英雄,我不才勉为其难吧

发表速度可能要慢下来了,找合适的例子进行演示和进行语言组织就不像前三章那么可以天马行空了.)


第四篇 ODbgScript的 注意事项和使用技巧

第四卷 OllyDBG 插件系列 第四篇 ODbgScript的 注意事项和使用技巧

作 者: hnhuqiong

ODbgScript还在不断的完善中,它是一个脚本解释器,那么它需要把用户的脚本命令一一解释出来并执行,你必须严格复合它的命令规范它才能解释你的命令.ODbgScript程序是这样定义的:

命令 目的操作数,源操作数 [,附加操作数]

命令:必须是ODbgScript能识别的命令,我前3章已经讲解了,不过今后命令不断的扩展中

操作数:

HEX : 既没有前缀也没有后缀

DEC : 在后缀中加点.

VAR : 变量，这个变量必须在使用前用Var进行定义

32REG: 32位寄存器 (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP)

16REG: 16位寄存器 (AX, BX, CX, DX, SI, DI, BP, SP)

8REG: 8位的寄存器(AL, AH, BH,BL,CH,CL, DL, DH)

[ ] : 被中括号括起来的内存地址 而[]中可以是数值,寄存器,变量

FLG :一个标志位，带有感叹号前缀(!CF, !PF, !AF, !ZF, !SF, !DF, !OF)

## :HEX的数据系列

"" :字符串系列

?? :包含??的HEX系列或者字符串系列,代表的是可以代表任意数

{} :过程计算符,中间可以放的是变量

: :标号,跟在你命令的名字后面,代表对程序分段

本来1,47版可以支持+-*/&|的操作数,这个纯粹是C爱好者的一相情愿,也让脚本变得可读性和通用性大大下降,我准备在后续版本中拿掉这个功能.看大家的意愿吧.这里以看雪的《脱壳基础知识入门（2006年版）》为蓝本来讲解,这样便于新手对照来学习:

第一课 PE格式章节,第三课 认识壳章节

我们知道一般能玩PE文件的大部分是用正常的编程语言才能取PE的东西,我们用脚本来完整的实现一次怎么知道PE信息.

(GMI将在下一个版本发布中增加大量的可获取PE信息的操作数,基本可以囊括整个PE结构的基本信息.这个命令不光查exe的信息,同样可以查DLL的详细见发布版readme)

脚本一:取得有用的PE信息

//

var modulebase

var DOSstub

var PEheard

var codebase

var codesize

var ImportAddressTable

var IATsize

var temp

var tmp

var OrignalFirstThunk

var TimeDateStamp

var ForwardChain

var Allname

var FirstName

var FirstThunk

var Namesize

//获得基本地址

getbaseinfo:

gmi eip,MODULEBASE

mov modulebase,$RESULT

gmi eip,CODEBASE

mov codebase,$RESULT

gmi eip,CODESIZE

mov codesize,$RESULT

//寻找DOS头

mov DOSstub,[modulebase],2 //只取2个字节,这个小技巧很多大侠都不知道 :)

cmp DOSstub,5a4d //"MZ"

jne end

//寻找PE头地址

find modulebase,#50450000# // "PE"

mov PEheard,$RESULT

//寻找IAT相关数据

mov temp,PEheard

add temp,80 //PE文件头偏移80处放的就是IAT

mov temp,[temp]

add temp,modulebase

mov ImportAddressTable,temp //取得IAT的地址

mov temp,PEheard

add temp,84 //PE文件头偏移84处放的就是IAT大小

mov temp,[temp]

mov IATsize,temp //取得IAT大小

//你将看见第一个DLL的API

mov temp,[ImportAddressTable+C]

add temp,modulebase

find temp,#0000#

sub $RESULT,temp

mov Namesize,$RESULT //求得ALlName的大小

readstr [temp],Namesize //读出ALLName

mov AllName,$RESULT

find temp,#00#

sub $RESULT,temp

mov Namesize,$RESULT //求得第一个Name的大小

readstr [temp],Namesize //读出Name

mov Name,$RESULT

mov temp,[ImportAddressTable+10]

mov FirstThunk,[temp+modulebase] //求出FirstThunk

gn FirstThunk //查询FirstThunk的符号名

mov tmp,$RESULT

//在调试文件所在目录里向PE.txt文件写信息

eval "modulebase{modulebase}"

wrt "PE.txt",$RESULT

eval "codebase{codebase}"

wrta "PE.txt",$RESULT

eval "codesize{codesize}"

wrta "PE.txt",$RESULT

eval "ImportAddressTable{ImportAddressTable}"

wrta "PE.txt",$RESULT

eval "IATsize{IATsize}"

wrta "PE.txt",$RESULT

eval "AllName{AllName}"

wrta "PE.txt",$RESULT

eval "Name{Name}"

wrta "PE.txt",$RESULT

eval "FirstThunk{FirstThunk}{tmp}"

wrta "PE.txt",$RESULT

end:

ret

//

呵呵,我写VC同样的程序还没有它清晰好读,到处是指针.你看这个多清爽!!!

(注意:这个版本里面加了命令readstr,请下载最新ODbgScript)

第二课 SEH技术 SEH脚本规避技术

其实,SEH挺死的,它的结构和原理在看雪已经被各位大侠研究透了,核心关键是抓住SEH的就是异常处理的句柄,它让异常的程序能回归进入正常的流程.我这里要强调一点,请小心使用你的硬件断点.至于为什么你去看SEH原理.所以请大家都检查一下OD,看选项-调试一栏中,不要钩上硬件断点,否则你每次F7和F8都是下的硬件断点.硬件断点最好在需要的时候再下.

第六课 寻找OEP

(1)根据跨段指令寻找OEP和根据堆栈平衡原理找OEP

ESP定律的发现和不断的完善是脱壳界一个重大进步,我们知道,壳无论怎么玩都是要交回原运行环境给程序,这样就暴露出它的软肋,我们定位好堆栈的数据,只要某段时刻一旦两个环境重合,就说明壳转了一圈回到了初始的环境,即便它不马上跳去OEP.但是,从堆栈平衡的原理来说它完成了它的一个轮回.原理上来说,只要我们能熟练的把握好"堆栈平衡原理",那么你就能够在各种壳的风口浪尖上不走迷失了方向.我个人也认为"堆栈平衡原理"是对付壳的一个重大武器,我们可以不断的在这个基础上进行深化和突破.

我们常常遇见ESP定理"失效"的时候,其实,它并没有失效,而是你不要把ESP定理机械的理解为就是OEP处,,就像我们知道的两点决定一线一样简单的定律,你只是简单的想成一根线,那么,你还能对付考大学路上的题海么? :)

真正掌握ESP定律,真正吸取ESP的精髓,壳等待你的就是你无比尖锐的利剑...

我曾经用ESP原理破掉了90%的壳,一个很简单的方法,我写过一个程序,就死死盯住原始环境,你走多少次我记录多少次,然后把纪录一看再作些工作统计一下,OEP绝大部分原形毕露...方法笨是笨点,但找OEP准确率太高了.

这里是一个简单的ESP定律脚本,主要是对付一些简单的压缩壳和保护壳,毕竟是教学,就不把它写复杂了.

这里用了几个小技巧就是靠单步来寻找pushad,并且避开当前就是pushad.并且还原堆栈以前的状态.

由于是脚本直接用指令改了EIP,所以,屏幕并没有刷屏,大家要双击一下EIP就是可以看见堆栈还原后的状态了.

脚本三:ESP定律

//

Start:

msgyn "你是选择pushad/popad方式吗(是)?还是狭义ESP方式(否)"

cmp $RESULT,1

jne esp

mov tmp,[eip],1

cmp tmp,60 //比较当前指令是否是pushad(60)如果是转ESP处理方式

je esp

findpushad: //如果不是就一直找到pushad(60)的地址

find eip,#60#

cmp $RESULT,0

je findtmp

mov tmp,[eip],1

cmp tmp,60

jne findok

sti

jmp esp

findtmp:

sti

jmp findpushad

findok:

bp $RESULT

esto

bc $RESULT

esp:

mov tmp,esp

sub tmp,4

sti

bphws tmp,"w"

esto

bphwcall

preop eip //向上还原堆栈状态.

mov oep,$RESULT

add esp,4

mov eip,oep

msg "你已经到达ESP平衡处"

end:

ret // 结束脚本

//

(3)根据编译语言特点找OEP

这个没有什么可教学的,就是需要你平时在计算机里面搜集一下各种没有加过壳的小程序,建立常规程序的搜集库,这些小文件可以很好的教你如何识别OEP的规律,各类语言编译的文件入口点都有一些规律的,在于你是否熟悉它,千变万化不离其宗.重要的是现在壳开始大规模偷OEP的头了,这样就很难用一般方法来简单定位OEP了,偷归偷,可环境它不能乱来,它只能来仿真以前的语句来实现被偷OEP的运行模式,壳现在的确还没有达到IA高度智能化的境界.比如我们可以知道VC5-6有一些版本的首个API就是调用GetVersion这个函数,可现在的壳大部分是把这个call给偷的没有影了,怎么办???

呵呵,小猫腻,我们用脚本来一句就可以定位它.

tocnd "eax 0A280105"

只要是VC5-6某些带GetVision的版本的程序,它一定要返回固定的版本号(只要你机器装好了XP操作系统这个0A280105就一定是它,是2000的话你自己查一下),机器是傻的人是活的,包裹的再好你也要漏一丝缝,Themida就是被我用这个简单的方法在GetVersion这个call中锁的死死的,不管你走多少步,我对照寄存器环境的变换,就可以锁死你的VM在干什么!实在懒得在IDA中分析了,就这个方法破花指令和VM是特别有效的. (小提示:这些VC5-VC6的程序,你只要OD能完整的跑一次,就可以用我这个方法跑到GetVersion这个CALL上不管它怎么偷,当然你的CPU要够强劲,剩下的事情你可以去看连续剧了,慢是慢点,剩心剩事,顺便还可以把oep被偷的头记录的完完整整)