随心逆向破解论坛,全网火爆原创辅助基地,集技术交流、视频教程、资源共享、游戏辅助,网站源码,辅助破解,手机软件破解,软件开源,专注收集并分享.各类教程

 找回密码
 立即注册
论坛邀请码购买 论坛邀请码购买 论坛邀请码购买 论坛邀请码购买 论坛邀请码购买
小玲辅助 678游戏辅助 小玲辅助 辅助购买24自动发卡 辅助购买24自动发卡
鼠神辅助网 无限流量咔俛费办/送主站/无限分站 无限流量咔俛费办/送主站/无限分站 【站长推荐】网创VIP会员20/月 文字广告位120/月
文字广告位120/月 文字广告位120/月 文字广告位120/月 易语言5.8完美破解版 易语言 v5.9.0 完美破解版
随心论坛安卓 逆向工具1.6 随心论坛安卓逆向工具1.6 随心论坛虚拟机10.0 随心论坛虚拟机10.0 随心论坛虚拟机12.7
随心论坛虚拟机12.7 随心论坛虚拟机41.1 随心论坛虚拟机41.1 随心破解论坛工具集1.8 随心破解论坛工具集1.8
快手极速版秒得8--60元 随心辅助万能通杀器 随心辅助万能通杀器 随心通杀器使用教程 随心通杀器使用教程
支付宝扫红包必得现金红包 支付宝扫红包必得现金红包 花贝 信用咔 白条 分期乐 vx收猪/三百一天/日节 网站搭建
★ 网站代更新 ★ 外链资源-5元一条 文字广告位120/月 随心创业项目网 随心创业项目网
随心创业项目网 随心创业项目网 文字广告位120/月 文字广告位120/月 文字广告位120/月

温馨提示:请在上面搜索| 查找更多免费资源,如需广告位请联系站长QQ:1469437475

全站
4天前

OllyDbg完全教程二一,Hit跟踪[Hit trace]

2022-07-16 05:41:29 发布

逆向图文 /[原创逆向图文] 142444473 0 0

发表于 2022-7-16 05:41:29 | 显示全部楼层 |阅读模式
二一,Hit跟踪[Hit trace]

Hit 跟踪能够让您辨别哪一部分代码执行了,哪一部分没有。OllyDbg的实现方法相当简单。它将选中区域的每一条命令处均设置一个INT3断点。当中断发生的时候,OllyDbg便把它去除掉,并把该命令标志为命中[hit]。因为每个跟踪断点只执行一次,所以这种方法速度非常快。

在使用 Hit跟踪的时候,一定要注意不能在数据中设置断点,否则应用程序极有可能崩溃。因此,您必须打开相关的菜单选项,以进行代码分析[analyze]。我推荐您选择严格或启发式函数识别[strict or heuristical procedure recognition]。如果选择模糊[Fuzzy]的话,可能会产生很多难以容忍的错误,而且经常把本不是函数的代码段识别成函数。

只要您在模块中设置了跟踪断点,哪怕只设了一个,OllyDbg都会分配两倍于代码段大小的缓冲区。

注意:当您退出Hit跟踪的时候,Run跟踪也会同时退出。

Run 跟踪[Run trace]
Run 跟踪是一种反方向跟踪程序执行的方式,可以了解以前发生的事件。您还可以使用Run跟踪来了解运行的简单统计[profile]。基本上,OllyDbg 是一步一步地执行被调试程序的,就像动画[animation]演示一样,但不会实时刷新窗口,最重要的是它能将地址、寄存器的内容、消息以及已知的操作数记录到Run跟踪缓冲区中。如果被调试的代码是自修改的,您就能够保存原始的命令。可以通过按Ctrl+F11(Run跟踪步入,进入子函数)或者 Ctrl+F12(Run跟踪步过,一次执行完子函数)开始Run跟踪,并用F12或者Esc键停止跟踪。

您可以指定在Run跟踪时执行每一步的条件集(快捷键:Ctrl+T)。如果条件符合,Run跟踪将暂停。条件包括:


?当EIP在某个地址范围内时暂停[Pause when EIP is in the address range];
?当EIP在某个地址范围之外时暂停[Pause when EIP is outside the address range];
?当某个条件为真时暂停[Pause when some condition is true];
? 当下一条指令可疑时暂停[Pause when next command is suspicious],比如:可能为非法指令(根据在分析3[Analysis 3]中设定的规则而定),访问不存在的内存,设置了单步陷阱标志[single-step trap flag]或者越ESP界访问栈。注意这个选项会明显地(大约20%)减慢Run跟踪的速度;

?当命令执行达到指定的次数(更确切的说,是添加到Run跟踪的缓冲区里面的命令数量)时暂停[Pause after specified number of commands is traced]。注意计数器不能自动归零。也就是说,如果您设置指令次数为10,则在第10次执行到该命令时暂停,并不是该命令每执行10次就暂停一次。

? 当下一条命令符合指定的样式之一时暂停[Pause when next command matches one of the specified patterns]。您可以使用模糊命令和操作数[imprecise commands and operands]及匹配32位寄存器RA和RB,像R32一样,这两个寄存器可以替代任何通用32位寄存器,但是在同一条命令中其值是不能变的。而 RA 和 RB
在同一条命令中,则一定是不同的。例如,在程序中含有 XOR EAX,EAX; XOR ESI,EDX 两条命令,两条命令均符合样式 XOR R32,R32;第一条命令符合样式XOR RA,RA
;而等二条命令 XOR ESI,EDX 符合样式XOR RA,RB。

毫无疑问,Run跟踪需要足够的内存,每条命令平均需要占用16到35字节,同时速度也非常慢。在500-MHZ处理器、Windows NT环境下,它每秒能跟踪5000条指令。
Windows95 更慢:每秒钟仅2200条指令。但是在许多情况下,例如当一个程序跳转到不存在的地址的时候,这是找到原因的唯一方法。您可以在Run跟踪时将准线性命令序列(即序列尾部只有唯一出口)跳过。当OllyDbg遇到这些需跳过的命令序列时,会设置一个临时断点,然后跟进到序列中,并一次运行完。当然了,如果排除命令中返回或跳转的地址在跟踪范围之外,将可能导致跟踪发生错误;因此OllyDbg会检查您想跳过的代码块,如果存在上述情况,会向您询问。

在大多数情况下,您对跟踪系统API代码不感兴趣。跟踪选项总是跟过系统DLL[Always trace over system DLLs]允许您在跟踪/自动模式下跟过API函数。如果模块在系统目录下,OllyDbg就假设该模块是系统的。您可以在模块[Modules]窗口中标记任意DLL是系统的或者非系统的。

为了使执行速度更快,您可以通过设置Run跟踪断点,先将Run跟踪限制在选定的命令或代码块上,然后再运行程序。我把这种做法称作“强迫Run跟踪”。一般来说,删除Run跟踪断点不会移除Hit跟踪断点。但如果您删除了hit跟踪断点,同时您也移除了Run跟踪断点。


跟踪命令会保存到跟踪缓冲区中,这个缓冲区在跟踪开始时自动创建。您可以在选项中指定它的大小(最高64MB)。这个缓冲区是循环队列,当满了的时候,会丢弃老的记录。

您可以通过从OllyDbg主菜单中选择“调试[Debug]|打开或者清除Run跟踪[Open or clear run trace]”,来打开或者清除Run跟踪缓冲区。在Run跟踪缓冲区打开后,
OllyDbg 会记录在执行过程中的所有暂停,甚至那些不是由Run跟踪引起的暂停。例如,您可以通过按 F7 或者 F8 单步执行程序,然后通过使用+键和-键来反方向跟踪程序的执行。注意:如果Run跟踪缓冲区已经关闭,则用这些键浏览的是历史[history]记录。在您查看Run跟踪记录时,寄存器和信息面板会变灰,来强调它们所显示的寄存器并不是实际的寄存器。跟踪缓冲区并不保存栈顶或由寄存器所指向的内容。寄存器、信息和栈在Run跟踪的时候使用实际的内存状态来解释寄存器的变化。

OllyDbg能够记下每个指令在Run跟踪缓冲区里面出现的次数。在反汇编窗口快捷菜单中,选择是“查看[View]|统计作为注释[Profile as comments]”。这个命令使用统计取代了注释栏。或者,如果列标题栏可见,则可以单击它几次直到它显示统计信息。注意显示出来的数字是动态的,而且不计算已经从跟踪缓冲区中丢弃的指令。您还可以在单独的统计窗口[Profile window]中,按触发次数排序,来查看整个模块的统计数据。

在反汇编窗口的快捷菜单中选择“Run跟踪[Run trace]|添加到所有函数入口处[Add entries of all procedures]”,这样能够检查每个可识别的函数被调用的次数。另一个命令“Run跟踪[Run trace]|添加到函数中所有的分支[Add branches in procedure]”会强行跟踪此函数中所有识别的跳转目的地址的内容。在这种情况下,统计功能能够找到最频繁执行的分支,您可以优化这部分的代码,以提高速度。

在反汇编窗口中的某条命令上使用快捷菜单中选择“搜索[Search for]|Run跟踪的最新记录[Last record in run trace]”用于查找该命令是否被执行过,如果执行过,最后一次执行在哪里。


Run 跟踪窗口显示跟踪缓冲区的内容。对每个指令来说包括被指令改变的整数寄存器的内容(更准确的说是给定的记录变成下一条记录的变化)。如果您双击某条指令,窗口会选择在跟踪缓冲区里全部含有该命令的记录,而且您可以通过按+和-键来快速的浏览;如果您在调试选项[Debugging options]中设置了 “跟踪[Trace]|同步CPU和
Run跟踪[Synchronize CPU and Run trace]”,双击记录则会跟进到对应的反汇编窗口中位置。

注意:当您退出Hit跟踪时,您同时也强行退出了Run跟踪。

通用快捷键[Global shortcuts]

无论当前的OllyDbg窗口是什么,这些快捷键均有效:


Ctrl+F2 - 重启程序,即重新启动被调试程序。如果当前没有调试的程序,OllyDbg会运行历史列表[history list]中的第一个程序。程序重启后,将会删除所有内存断点和硬件断点。
译者注:从实际使用效果看,硬件断点在程序重启后并没有移除。

Alt+F2 - 关闭,即关闭被调试程序。如果程序仍在运行,会弹出一个提示信息,询问您是否要关闭程序。

F3 - 弹出“打开32位.EXE文件”对话框[Open 32-bit .EXE file],您可以选择可执行文件,并可以输入运行参数。

Alt+F5 - 让OllyDbg总在最前面。如果被调试程序在某个断点处发生中断,而这时调试程序弹出一个总在最前面的窗口(一般为模式消息或模式对话框[modal message or dialog]),它可能会遮住OllyDbg的一部分,但是我们又不能移动最小化这个窗口。激活OllyDbg(比如按任务栏上的标签)并按Alt+ F5,OllyDbg将设置成总在最前面,会反过来遮住刚才那个窗口。如果您再按一下Alt+F5,OllyDbg会恢复到正常状态。OllyDbg是否处于总在最前面状态,将会保存,在下一次调试时依然有效。当前是否处于总在最前面状态,会显示在状态栏中。

F7 - 单步步入到下一条命令,如果当前命令是一个函数[Call],则会停在这个函数体的第一条命令上。如果当前命令是是含有REP前缀,则只执行一次重复操作。

Shift+F7 - 与F7相同,但是如果被调试程序发生异常而中止,调试器会首先尝试步入被调试程序指定的异常处理(请参考忽略Kernel32中的内存非法访问)。

Ctrl+F7 - 自动步入,在所有的函数调用中一条一条地执行命令(就像您按住F7键不放一样,只是更快一些)。当您执行其他一些单步命令,或者程序到达断点,或者发生异常时,自动步入过程都会停止。每次单步步入,OllyDbg都会更新所有的窗口。所以为了提高自动步入的速度,请您关闭不必要成窗口,对于保留的窗口最好尽量的小。按Esc键,可以停止自动步入。

F8 - 单步步过到下一条命令。如果当前命令是一个函数,则一次执行完这个函数(除非这个函数内部包含断点,或发生了异常)。如果当前命令是含有REP前缀,则会执行完重复操作,并停在下一条命令上。

Shift+F8 - 与F8相同,但是如果被调试程序发生异常而中止,调试器会首先尝试步过被调试程序指定的异常处理(请参考忽略Kernel32中的内存非法访问)。

Ctrl+F8 - 自动步过,一条一条的执行命令,但并不进入函数调用内部(就像您按住F8键不放一样,只是更快一些)。当您执行其他一些单步命令,或者程序到达断点,或者发生异常时,自动步过过程都会停止。每次单步步过,OllyDbg都会更新所有的窗口。所以为了提高自动步过的速度,请您关闭不必要成窗口,对于保留的窗口最好尽量的小。按Esc键,可以停止自动步过。

F9 - 让程序继续执行。

Shift+F9 - 与F9相同,但是如果被调试程序发生异常而中止,调试器会首先尝试执行被调试程序指定的异常处理(请参考忽略Kernel32中的内存非法访问)。

Ctrl+F9 - 执行直到返回,跟踪程序直到遇到返回,在此期间不进入子函数也不更新CPU数据。因为程序是一条一条命令执行的,所以速度可能会慢一些。按Esc键,可以停止跟踪。

Alt+F9 - 执行直到返回到用户代码段,跟踪程序直到指令所属于的模块不在系统目录中,在此期间不进入子函数也不更新CPU数据。因为程序是一条一条执行的,所以速度可能会慢一些。按Esc键,可以停止跟踪。

Ctrl+F11 -Run跟踪步入,一条一条执行命令,进入每个子函数调用,并把寄存器的信息加入到Run跟踪的存储数据中。Run跟踪不会同步更新CPU窗口。

F12 - 停止程序执行,同时暂停被调试程序的所有线程。请不要手动恢复线程运行,最好使用继续执行快捷键或菜单选项(像 F9)。

Ctrl+F12 - Run跟踪 步过,一条一条执行命令,但是不进入子函数调用,,并把寄存器的信息加入到Run跟踪的存储数据中。Run跟踪不会同步更新CPU窗口。

Esc - 如果当前处于自动运行或跟踪状态,则停止自动运行或跟踪;如果CPU显示的是跟踪数据,则显示真实数据。

Alt+B - 显示断点窗口。在这个窗口中,您可以编辑、删除、或跟进到断点处。

Alt+C - 显示CPU窗口。

Alt+E - 显示模块列表[list of modules]。

Alt+K - 显示调用栈[Call stack]窗口。

Alt+L - 显示日志窗口。

Alt+M - 显示内存窗口。

Alt+O - 显示选项对话框[Options dialog]

Ctrl+P - 显示补丁窗口。

Ctrl+T - 打开 暂停 Run跟踪 对话框

Alt+X - 关闭 OllyDbg。

大多数窗口都支持以下的键盘命令:


Alt+F3 - 关闭当前窗口。

Ctrl+F4 - 关闭当前窗口。

F5 - 最大化当前窗口或将当前窗口大小改为正常化。

F6 - 切换到下一个窗口。

Shift+F6 - 切换到前一个窗口。

F10 - 打开与当前窗口或面板相关的快捷菜单。

左方向键 - 显示窗口左方一个字节宽度的内容。

Ctrl+左方向键 - 显示窗口左方一栏的内容。

右方向键 - 显示窗口右方一个字节宽度的内容

Ctrl+右方向键 - 显示窗口右方一栏的内容


反汇编窗口中的快捷键[Disassembler shortcuts]

当CPU窗口中的反汇编面板[Disassembler pane]处于激活状态时,您可以使用以下快捷键:

回车键 - 将选中的命令添加到命令历史[command history]中,如果当前命令是一个跳转、函数或者是转换表的一个部分,则进入到目的地址。

退格键 - 移除选中部分的自动分析信息。如果分析器将代码误识别为数据,这个快捷键就非常有用。请参考解码提示[decoding hints].

Alt+退格键 - 撤消所选部分的修改,以备份数据的相应内容替换所选部分。仅当备份数据存在且与所选部分不同时可用。

Ctrl+F1 -如果API帮助文件已经选择,将打开与首个选择行内的符号名相关联的帮助主题。

F2 -在首个选择的命令上开关INT3 断点[Breakpoint],也可以双击该行第二列。

Shift+F2 -在首个选择命令设置条件断点,参见忽略Kernel32中内存访问异常[Ignore memory access violations in Kernel32]。

F4 -执行到所选行,在首个选择的命令上设置一次性断点,然后继续执行调试程序,直到OllyDbg捕获到异常或者停止在该断点上。在程序执行到该命令之前,该一次性断点一直有效。如有必要,可在断点窗口[Breakpoints window]中删除它。

Shift+F4 -设置记录断点(一种条件断点,当条件满足时一些表达式的值会记录下来), 详情参见断点[Breakpoint]。

Ctrl+F5 -打开与首个选择的命令相对应的源文件。

Alt+F7 -转到上一个找到的参考。

Alt+F8 -转到下一个找到参考。

Ctrl+A -分析当前模块的代码段。

Ctrl+B - 开始二进制搜索。

Ctrl+C -复制所选内容到剪贴板。复制时会简单地按列宽截断不可见内容,如果希望排除不需要的列,可把这些列的宽度调整到最小。

Ctrl+E -以二进制(十六进制)格式编辑所选内容。

Ctrl+F -开始命令搜索。

Ctrl+G -转到某地址。该命令将弹出输入地址或表达式的窗口。该命令不会修改 EIP。

Ctrl+J -列出所有的涉及到该位置的调用和跳转,在您用这个功能之前,您必须使用分析代码功能。

Ctrl+K - 查看与当前函数相关的调用树[Call tree]。在您用这个功能之前,您必须使用分析代码功能。

Ctrl+L - 搜索下一个,重复上一次的搜索内容。

Ctrl+N - 打开当前模块的名称(标签)列表。

Ctrl+O - 扫描object文件。扫描Object文件。该命令会显示扫描Object文件对话框,您可以在该对话框中选择Object文件或者lib文件,并扫描这个文件,试图找到在实际代码段中用到的目标模块。

Ctrl+R -搜索所选命令的参考。该命令扫描激活模块的全部可执行代码,以找到涉及到首个选中的命令的全部相关参考(包括:常量、跳转及调用),您可以在参考中使用快捷键 Alt+F7 和 Alt+F8来浏览这些参考。为便于您使用,被参考的命令也包含在该列表中。

Ctrl+S -命令搜索。该命令显示命令查找[Find command]对话框供您输入汇编命令,并从当前命令开始搜索。

星号[Asterisk](*) -转到原始位置(激活线程的EIP处)。

Ctrl+星号(*) - 指定新的起始位置,设置当前所选线程的EIP为首个选择字节的地址。您可以在选择EIP并撤消该操作。

加号[Plus](+) -如果run跟踪[run trace] 没有激活,则根据命令历史[command history]跳到下一条运行过命令的地方;否则跳到Run跟踪的下一个记录。

Ctrl+加号 - 跳到前一个函数开始处。(注意只是跳到,并不执行)

减号[Minus](-) - 如果run跟踪[run trace] 没有激活,则根据命令历史[command history]跳到前一条运行过命令的地方;否则跳到Run跟踪的前一个记录。

Ctrl+减号 - 跳到下一个函数开始处。(注意只是跳到,并不执行)

空格[Space] - 修改命令。您可在显示对话框中以汇编语言修改实际指令或输入新指令,这些指令将替换实际代码,您也可以在想要修改的指令处双击鼠标。

冒号[Colon]( - 添加标签。显示添加标签窗口[Add label]或修改标签窗口[Change label],您可在此输入与首个选择的命令中的第一个字节相关联的标签(符号名)。注意,在多种编程语言中,冒号可以是标签的一部分。

分号[Semicolon](;) - 添加注释[comment]。显示添加注释窗口[Add label]或修改注释窗口[Change label],您可在此输入与首条所选命令的第一个字节相关联的注释(注释串会显示在最后一列中)。注意,多种汇编语言使用分号作为注释开始。您也可以在注释列双击需要注释的命令行。

插件[Plugins]

插件是一个DLL,存放在OllyDbg的目录中,用于增加 OllyDbg 的功能。您可以从 OllyDbg 的主页上(http://home.t-online.de/home/Ollydbg)免费下载插件开发工具包
plug110.zip。

插件可以设置断点,增加标签和注释,修改寄存器和内存。插件可以添加到主菜单和很多的窗口(比如反汇编窗口、内存窗口)的快捷菜单中,也可以拦截快捷键。插件还可以创建MDI(多文档界面)窗口。插件还可以根据模块信息和OllyDbg.ini文件,将自己数据写到.udd文件中;并能读取描述被调试程序的各种数据结构。插件API包含了多达170
个函数。

许多第三方插件都可以从Internet网上获得,比如由网友TBD创建并维护的OllyDbg的论坛(http://ollydbg.win32asmcommunity.net)。

安装插件的方法:将DLL复制到插件目录[plugin directory]中,然后重新启动Ollydbg。默认情况下,这个插件目录为ollydbg.exe文件所在的目录。

现在的版本中已经包含了两个“原始”插件: 书签[Bookmark] and 命令行[Command line]. 他们的源代码都保存在plug110.zip.文件中。这些插件都是免费的,您可以任意修改或使用它们。

技巧提示[Tips and tricks]

?OllyDbg 可以作为二进制编辑器使用。选择视图[View]→文件[File]并选定需要查看的文件。文件不能大于剩余内存数量。

?假使您修改了内存中的执行文件,这时您想恢复修改的部分,但是您忘记哪里被修改了,您可以把原始文件当作备份进行加载,这样您就可以找到修改的部分了。

分析前,先扫描 OBJ 文件。这时 OllyDbg 会对已知 C 函数的参数进行解码。

一些表格中包含了隐藏数据。可以通过增加列宽来显示出来。

所有数据窗口(包括反汇编窗口),可以通过双击显示相对的地址。

您可以通过 Ctrl +↑ 或 Ctrl+↓ 对数据窗口翻动一个字节。

调试独立的DLL[Debugging of stand-alone DLLs]

打开DLL,也可以直接将其从资源管理器拖放到 OllyDbg 上。OllyDbg 会询问您并将该文件的全路径作为参数传递给loaddll.exe.。然后链接库被加载并停在代码的入口(
<DllEntryPoint>)。您可以设置断点,运行或跟踪启动代码,等等。在初始化完成后,应该程序会再次暂停。这次停在标签名为 Firstbp 的位置,其在立即进入主消息循环之前。


现在,您可以调用DLL函数。从主菜单选择“调试[Debug]|调用DLL输出[Call DLL export]”。这时会弹出一个对话框。由于这个对话框是无模式对话框,因此您仍然能够使用OllyDbg的全部功能,比如查看代码、数据,查看断点,修改内存等等。
选择您想调用的函数。例如我们将开始使用 USER32.DLL 里的MessageBox 函数。注意loaddll.exe 已经使用了这个链接库,因此会假定这个 DLL 已经初始化而不再调用入口。MessageBox 这个函数名是通用函数名,实事上,这个函数有处理 ASCII 的 MessageBoxA 和处理 Unicode 的MessageBoxW 两种。我们继续往下看:



在我们选择这个函数后,右边的消息框中会出现 Number of arguments: 4(有四个参数)的字样。OllyDbg 会根据函数尾部的RET 10语句来正确识别参数的数量。RET nnn
是使用PASCAL调用约定的函数的典型特征。(参数被放入栈中,第一个参数会被最后一个压入栈中,函数调用完毕后,参数会被遗弃)。大多数的 Windows API 函数都是
PASCAL形式的。

下一步,我们要设定栈中参数的个数。在这个例子中,不必做进行这个操作,因为OllyDbg已经知道了MessageBoxW函数的参数数量。但是,如果您愿意的话,也可以单击左边的复选框,改变成您认为合适的参数数量

现在填写参数列表。这个对话框中支持至多10个参数. 参数可以是任何有效的表达式,而不必使用寄存器。如果操作数指向了内存,则参数右边的缓冲区窗口会显示内存中的数据。Loaddll.exe 有10个大小为1K的缓冲区,这些缓冲区被标记为Arg1 .. Arg10,,您可以方便自由的使用它们。另外,对话框还支持两个伪变量:由loaddll.exe创建的父窗口句柄<Hwnd>,以及loaddll的实例句柄<Hinst>。为了方便您的使用,在您第一次使用调用输出函数时,OllyDbg就已经将这两个伪变量加到了历史列表中去了。

MessageBoxW e函数需要4个参数:


?父窗口句柄。 这里我们选择<Hwnd> ;handle of owner window. Here, we simply select <Hwnd>;
?在消息框中UNICODE文本的地址。选择Arg2并按回车。缓冲区窗口会以16进制的格式显现内存中的缓冲区。这个缓冲区初始化全是0。点击第一个字节,并按快捷键
Ctrl+E(另外, 也可以从菜单中选择“二进制[Binary]|编辑[Edit]”)。这时会出现一个对话框,在对话框中键入“Text in box”或者其他希望显示的字符串;

?消息框标题的UNICODE文本的地址。选择Arg3并在Unicode格式的内存中写上“Box title”;
?消息框的风格。使用常量MB_xxx进行组合.OllyDbg 可以识别这些常量。在这里我们键入:MB_OK|MB_ICONEXCLAMATION。

这里不需要寄存器参数。

现在我们准备调用输出函数。选项“在调用时隐藏[Hide on call]”意思是说,当函数运行时对话框将会从屏幕消失。当我们执行一个会运行很长时间的函数,或者设置了断点的时候,这个选项非常的有用。您也可以手动关闭对话框。当函数执行完毕后,OllyDbg会重新自动打开。“调用输出函数”对话框。选项“在调用后暂停[Pause after call]”意思是说,在执行完函数后,loaddll将会被暂停。

按“调用[Call]按钮”后,OllyDbg 会自动备份所有的内存、校验、参数、寄存器等信息。并隐藏对话框,然后调用 MessageBoxW 函数。和期望的一样,消息框在屏幕中出现了:



函数 MessageBoxW 不会修改参数。如果您调用的函数更新了内存,比如函数 GetWindowName,修改的字节将会在数据区里高亮。注意:EAX 返回值为1,表示成功。

其他的例子请访问我的网站:
http://home.t-online.de/home/Ollydbg/Loaddll.htm.

不幸的是,您不能通过这种方式调试OllyDbg的插件,插件关联到ollydbg.exe文件,Windows系统不能在同一个应用程序里加载并运行两个可执行文件。

解码提示[Decoding hints]
在某些情况下,分析器不能区分代码和数据。让我们看看下面的例子:


const char s[11] = "0123456789";
...
for (i=0x30; i<0x3a; i++) t[i-0x30]=s[i-0x30];

好的编译器将会将上面的代码优化成如下样子: e


for (i=0x30; i<0x3a; i++) (t-0x30)=(s-0x30);

这里t-0x30 和 s-0x30 都是常量,并编译成如下形式:


MOV AL,[BYTE s_minus_30+EBX]
MOV [BYTE t_minus_30+EBX],AL

编译器也可能将常量字符串"0123456789"插入到执行代码中。在1.10版本中,我打算用寄存器的值来决定是否的数据或代码。当遇到上面的命令,分析器将假定地址
s_minus_30处包含字符数据。但事实上,可能那里是代码。

万一出现上述问题,我们应该怎么办呢?有两种办法:最快最笨的办法是:将分析错误的部分删除(快捷键:退格键),这样OllyDbg将使用默认的反汇编器进行解码。

更好的办法是使用解码提示[decoding hints]。您可以告诉OllyDbg如何解释选中的内存内容。这种方法在重新分析(Ctrl+A)时,解释依然有效。

设置提示的方法:在反汇编窗口中,选中需要修正提示的代码或数据,然后在快捷菜单中选择 分析[Analysis]|在下次分析时,将选择部分视为[During next analysis, treat selection as]。选择以下选项之一:


命令[Command] - 第一个被选中的字节开始的有效命令。这条命令,还有所有后面的部分,直到有Jump或Return命令出现,以及含有Jump或Call命令所到达位置的部分,都会被视为命令;

字节[Byte],
字[Word],
双字[Doubleword] - 选中的前1、2、4字节视为对应大小的数据;

所有选中命令[Commands] - 全部选中部分(直到第一个无效命令)和可以到达由有效命令集组成的目的地址;

字节[Bytes],
字[Words],
双字[Doublewords], - 全部选中部分以1、2、或 4字节分组;

ASCII字符串[ASCII text],
UNICODE字符串[UNICODE text] - 全部选中部分为ASCII 或 UNICODE 字符串;

默认(移除提示)[Default (remove hints)] - 从选中部分中移除全面提示;

移除全部提示[Remove all hints] - 从全部模块中移除解码提示。

OllyDbg 保存提示到.udd文件中。

表达式赋值[Evaluation of expressions]

[code]
OllyDbg能够支持非常复杂的表达式。表达式的语法格式将在这个主题的后面进行介绍,但我想您对此不一定真的感兴趣。那么我先举几个实例来说明:


10 - 常量 0x10 (无符号)。所有整数常量都认为是十六进制的,除非后面跟了点;

10. - 十进制常量10(带符号);

'A' - 字符常量 0x41;

EAX - 寄存器EAX的内容,解释为无符号数;

EAX. -寄存器EAX的内容,解释为带符号数;

[123456] - 在地址123456处的无符号双字内容。默认情况,OllyDbg假定是双字长操作数;

DWORD PTR [123456] - 同上。关键字 PTR 可选;

[SIGNED BYTE 123456] - 在地址123456处带符号单字节。OllyDbg支持类MASM和类IDEAL两种内存表达式;

STRING [123456] - 以地址123456作为开始,以零作为结尾的ASCII字符串。中括号是必须的,因为您要显示内存的内容;

[[123456]] - 在地址123456处存储的双字所指向的地址内的双字内容;

2+3*4 - 值为14。OllyDbg 按标准C语言的优先级进行算术运行;

(2+3)*4 - 值为20。使用括号改变运算顺序。

EAX.<0. - 如果EAX在0到0x7FFFFFFF之间,则值为0,否则值为1。注意0也是有符号的。当带符号数与无符号数比较时,OllyDbg会将带符号数转成无符号数。

EAX<0 - 总为0(假),因为无符号数永远是正的。

MSG==111 - 如果消息为WM_COMMAND,则为真。0x0111是命令 WM_COMMAND 的数值。MSG只能用于设置在进程消息函数的条件断点内。

[STRING 123456]=="Brown fox" - 如果从地址0x00123456开始的内存为ASCII字符串"Brown fox"、"BROWN FOX JUMPS"、 "brown fox???",或类似的串,那么其值为1。比较不区分大小写和文本长度。

EAX=="Brown fox" - 同上,EAX按指针对待。

UNICODE [EAX]=="Brown fox" - OllyDbg认为EAX是一个指向UNICODE串的指针,并将其转换为ASCII,然后与文本常量进行比较。

[ESP+8]==WM_PAINT - i在表达式中您可以使用上百种Windows API符号常量。

([BYTE ESI+DWORD DS:[450000+15*(EAX-1)]] & 0F0)!=0 - 这绝对是个有效的表达式。

现在我们介绍语法格式。在大括号({})内的每个元素都只能出现一次,括号内的元素顺序可以交换:


表达式 = 内存中间码|内存中间码<二元操作符>内存中间码

内存中间码 = 中间码| { 符号标志 大小标志 前缀} [表达式 ]

中间码 = (表达式)| 一元操作符 内存中间码 | 带符号寄存器 | 寄存器 | FPU寄存器 | 段寄存器 | 整型常量 | 浮点常量 | 串常量 | 参数 | 伪变量

一元操作符 = ! | ~ | + |

带符号寄存器 = 寄存器.

寄存器 = AL | BL | CL ... | AX | BX | CX ... | EAX | EBX | ECX ...

FPU寄存器 = ST | ST0 | ST1 ...

段寄存器 = CS | DS | ES | SS | FS | GS

整型常量 = <十进制常量>. | <十六进制常量> | <字符常量> | <API符号常量>

浮点常量 = <符点常量>

串常量 = "<串常量>"

符号标志 = SIGNED | UNSIGNED

大小标志 = BYTE | CHAR | WORD | SHORT | DWORD | LONG | QWORD | FLOAT | DOUBLE | FLOAT10 | STRING | UNICODE

前缀 = 中间码:

参数 = %A | %B     // 仅允许在监察器[inspector] 中使用

伪变量 = MSG     // 窗口消息中的代码

这个语法并不严格。在解释[WORD [EAX]]或类似的表达式时会产生歧义。可以理解为以寄存器EAX所指向地址的两字节内容为地址,所指向的双字内容;也可以理解为以寄存器EAX所指向地址的四字节内容为地址,所指向的两字节内容。而OllyDbg会将修饰符尽可能的放在地址最外面,所以在这种情况下,[WORD [EAX]] 等价于 WORD [[EAX]]。

默认情况下,BYTE、WORD 和 DWORD 都是无符号的,而CHAR、SHORT 和 LONG都是带符号的。也可以使用明确的修饰符SIGNED 或 UNSIGNED。例如在二元操作时,如果一个操作数是浮点的,那么另外一个就要转成浮点数;或者如果一个是无符号胆,那么另外一个要转成无符号的。浮点类型不支持UNSIGNED。大小修饰符后面跟 MASM兼容关键字PTR(如:BYTE PTR)也允许的,也可以不要PTR。寄存器名和大小修饰符不区分大小写。

您可以使用下面类C的运算符(0级最高):


优先级     类型     运算符
0     一元运算符     ! ~ + -
1     乘除运算     * / %
2     加减运算     + -
3     位移动     << >>
4     比较     < <= > >=
5     比较     == !=
6     按位与     &
7     按位异或     ^
8     按位或     |
9     逻辑与     &&
10     逻辑或     ||
在计算时,中间结果以 DWORD 或 FLOAT10 形式保存。某些类型组合和操作是不允许的。例如:QWODRD 类型只能显示;STRING 和 UNICODE 只能进行加减操作(像C语言里的指针)以及与 STRING、UNICODE 类型或串常量进行比较操作;您不能按位移动浮点[FLOAT] 类型,等等。

自定义函数描述[Custom function descriptions]

概论[Introduction]

OllyDbg包含(做为内部资源)1900多种标准函数以及400多种标准C函数的名称和参数。分析器[Analyzer] 用这些描述使被调试程序更加易懂。比较下面一个例子,分析器的函数CreateFont:


    PUSH OT.00469F2A           ; ASCII "Times New Roman"
    PUSH 12                  
    PUSH 2                  
    PUSH 0               
    PUSH 0                  
    PUSH 0                  
    PUSH 0               
    PUSH 0                  
    MOV EAX,DWORD PTR [49FA70]
    PUSH EAX
    PUSH 190               
    PUSH 0               

    PUSH 0               
    PUSH 0               
    PUSH 10               
    CALL <JMP.&GDI32.CreateFontA>

这是分析后的:


    MOV EAX,DWORD PTR [49FA70]  
    PUSH OT.00469F2A           ; ?FaceName = "Times New Roman"
    PUSH 12                 ; ?PitchAndFamily = VARIABLE_PITCH|FF_ROMAN
    PUSH 2                 ; ?Quality = PROOF_QUALITY
    PUSH 0                 ; ?ClipPrecision = CLIP_DEFAULT_PRECIS
    PUSH 0                 ; ?OutputPrecision = OUT_DEFAULT_PRECIS
    PUSH 0                 ; ?CharSet = ANSI_CHARSET

    PUSH 0                 ; ?StrikeOut = FALSE
    PUSH 0                 ; ?Underline = FALSE
    PUSH EAX               ; ?Italic => TRUE
    PUSH 190               ; ?Weight = FW_NORMAL
    PUSH 0                 ; ?Orientation = 0
    PUSH 0                 ; ?Escapement = 0
    PUSH 0                 ; ?Width = 0
    PUSH 10                 ; ?Height = 10 (16.)

    CALL <JMP.&GDI32.CreateFontA> ; ?CreateFontA

显然,后面的代码更容易理解。API函数CreateFont 有14个参数。分析器标记所有这些参数的名称并解码他们的值。如果寄存器跟踪开启,那么分析器同时会解码参数Italic
的值为地址49FA70处双字长的内容。解码使用参数的真实值,所以如果[49FA70]里的内容改变了,那么参数Italic的值也会随之改变。当EIP指向跳转或调用该函数的命令,或指向入口时,OllyDbg也会在栈中对已知函数的参数进行解码。

OllyDbg可以对像printf()这样参数个数可变的函数进行参数解码:

    PUSH EAX               ; ?<%.*s>
    PUSH E8                 ; ?<*> = E8 (232.)
    PUSH EBX               ; ?<%08X>
    PUSH Mymodule.004801D2       ; ?format = "Size %08X (%.*s) bytes"
    PUSH ESI               ; ?s
    CALL Mymodule.sprintf       ; ?sprintf

您可以定义自己的函数。每次您打开某个应用程序时,OllyDbg都会重新设置函数参数表并用内嵌描述添充这个表。然后尝试打开文件“< OllyDbg目录>\common.arg”和“<OllyDbg目录>\<应用程序名>.arg”,这里<应用程序名>使用8.3格式(DOS)被调试程序文件名(不带路径和扩展名)。

下面看一个简单的.arg文件实例:


    INFO Simple .ARG file that decodes CreateHatchBrush
    TYPE HS_X
      IF 0 "HS_HORIZONTAL"
      IF 1 "HS_VERTICAL"
      IF 2 "HS_FDIAGONAL"
      IF 3 "HS_BDIAGONAL"
      IF 4 "HS_CROSS"
      IF 5 "HS_DIAGCROSS"
      ELSEINT
    END
    TYPE COLORREF
      IF 0 "<BLACK>"
      IF 00FFFFFF "<WHITE>"
      OTHERWISE
      TEXT "RGB("
      FIELD 000000FF
      UINT
      TEXT ","

      FIELD 0000FF00
      UINT
      TEXT ","
      FIELD 00FF0000
      UINT
      TEXT ")"
    END
    STDFUNC CreateHatchBrush
      "style" HS_X
      "colorref" COLORREF
    END

标准Windos API函数CreateHatchBrush(int style,int colorref) 有两个参数。第一个必须是阴影风格[hatch style],第二个是常量由红色、绿色、蓝色组成,并用一个32
位整数的低三字节表示。为了解码这些参数,文件定义了两个新的参数类型:HS_X 和 COLORREF。

阴影风格是一个简单的枚举类型,如0表示HS_HORIZONTAL(水平风格)、1表示HS_VERTICAL(垂直风格)。IF关键字比较参数与第一个操作数(注意:其总是十六进制的),如果相同则显示第二个操作数里的文本。但万一匹配失败会如何?关键字ELSEINT 会然OllyDbg会将参数解释为一个整数。

COLORREF 更复杂一些。首先尝试解码两个广泛使用的颜色值:黑(全0组成)与白(全0xFF组成)。如果匹配失败,COLORREF尝试解码颜色为一个结构包含红、绿、蓝的亮度。FIELD会用第一个操作数与参数进行逻辑与操作。然后转换结果为整数,并同时按位右移第一个操作及该整数,直到第一个操作数的二进制个位数字为1,这时整数按位右移的结果以无符号10进制显示出来。这个例子做了三次这样的操作,以分离出每个颜色成份。TEXT关键字用于无条件显示文本。如果参数为00030201,那么
COLORREF将其解码为RGB(1.,2.,3.)。

大多断API函数都会从栈中移除参数并保护寄存器EBX, EBP, ESI 和 EDI。声明这样的函数为STDFUNC,以告诉分析器该函数做了这样的事情。否则请其描述为FUNCTION


万一某个参数由多个域及比特值组成,比如上面提到的fdwPitchAndFamily ,我们该怎么办?请看下面这个例子:


    TYPE FF_PITCH
      MASK 03
      IF 00 "DEFAULT_PITCH"
      IF 01 "FIXED_PITCH"
      IF 02 "VARIABLE_PITCH"
      ELSEHEX
      TEXT "|"
      MASK 0C
      BIT 04 "4|"
      BIT 08 "8|"
      MASK FFFFFFF0
      IF 00 "FF_DONTCARE"
      IF 10 "FF_ROMAN"
      IF 20 "FF_SWISS"
      IF 30 "FF_MODERN"
      IF 40 "FF_SCRIPT"
      IF 50 "FF_DECORATIVE"
      ELSEHEX
    END

前两个比特位(第0和等1位)表示倾斜度,必须一起解码。我们使用 MASK 03 来提取这两个比特并通过IF序列来解码。增加了连接符“|”,分别提取第2和第3个比特位,并分别单独解码。最后提取剩余部分并进行解码。

OllyDbg 会移除生成串尾部的连接符“|”、空格、冒号、逗号、分号和等号。

目前版本的分析仅能够解码32位参数。如您不能解码双精度浮点或长双精度浮点的函数参数。


格式描述

自定义解码信息由函数描述和类型描述两部分组成。函数描述部分非常的简单:


FUNCTION|STDFUNC [模块名]函数名
<第一个参数的名称> <第一个参数的类型>
……
<最后一个参数的名称> <最后一个参数的类型>
END

如果函数从栈中移除参数并保护寄存器EBX, EBP, ESI 和 EDI,请使用关键字STDFUNC。大多少函数都遵循这样的规则。其他情况则声明为FUNCTION。模块(EXE 或 DLL)名是可选的。如果模块名被忽略,OllyDbg会对尝试匹配任何模块。模块名不区分大小写。

函数名称总是区分大小写的。有针对UNICODE的函数必须使用后缀 A 或 W 加以区分,比如SetWindowTextA.。

参数的顺序又C风格的参数使用惯例一致。而16位Windows和32位API函数也是按惯例使用。如果参数名由多个字组成,或者包含特殊字符,那么请将其用两个单引号引起来。与在C语言中一样,省略号(叄┦且桓鎏厥獾募锹加糜诒硎静问靠杀洹K匦朐诤枋龅淖詈蟆llyDbg不会尝试解码这样的参数。如果函数的参数为空,则按functionname(void)对待

OllyDbg 仅支持32位的参数。某些参数已经预定义好了:


INT         以十六进制和带符号整数两种格式显示值
UINT         以十六进制和无符号整数两种格式显示值
HEX         以十六进制格式显示值
BOOL         TRUE 或 FALSE
CHAR     ASCII 字符
WCHAR     UNICODE 字符
FLOAT     32位浮点数
ERRCODE     系统错误代码(像由函数GetLastError()报告的)
ADDR, PTR         地址(特殊情况:NULL)
ASCII               ASCII 串指针
UNICODE             UNICODE 串指针
FORMAT     在类似函数printf()(不包括wscanfW()!)使用的 ASCII 格式串
WFORMAT     类似函数wsprintfW()(不包括scanf()!)使用的 UNICODE 格式串
RECT     RECT(矩形)结构指针
MESSAGE     MSG(ASCII 窗口消息)结构指针
WMESSAGE     MSG(UNICODE 窗口消息)结构指针
HANDLE         句柄(特殊情况:NULL, ERROR_INVALID_HANDLE)
HWND     窗口句柄
HMODULE     模块句柄
RSRC_STRING           带索引的资源串
NULL, DUMMY               有参数,但解码时跳过了
您不能重定义预定义类型。自定义类型允许您将参数分离成几个域并分别解码。类型描述有以下几种格式:


TYPE 类型名
[TEXT "任何文本"]
[<域选择器>]
<域解码>
<域解码>
[TEXT "任何文本"]
[PURGE]
...
<域选择器>
<域解码>
<域解码>
[TEXT "任何文本"]
END

类型名的程度限制在16个字符以内。 OllyDbg会无条件将"任何文本"作为生成的解码。域选择器提取一部分参数用于解码。以下域选择器,可以用于提取域:


MASK 十六进制掩码 - 域等于参数同十六进制掩码按位与(AND)的结果。

FIELD 十六进制掩码 - 参数同十六进制掩码按位与(AND)的数值,然后OllyDbg同时按位右移掩码和计算的数值直到掩码的二进制个位为1,这时数值按位右移的结果就是域的值。例如参数0xC250, FIELD F0,得到的结果是5。

SIGFIELD 十六进制掩码 -参数同十六进制掩码按位与(AND)的数值,然后OllyDbg同时按位右移掩码和计算的数值直到掩码的二进制个位为1,这时数值按位右移的结果转成带符号32位数就是域的值。例如参数0xC250 ,SIGFIELD FF00,得到的结果是0xFFFFFFC2。

简单域的解码会一次显示整个域的内容:


HEX - 以十六进制形式显示域内容;

INT - 以带符号十进制形式显示域内容(带小数点);

UINT -以无符号十进制形式显示域内容(带小数点);

CHAR - 以 ASCII 字符形式显示域内容。

域若是一个枚举类型,则可以使用IF序列,如果必要的话还可以在IF序列后跟关键字 TRYxxx 与 ELSExxx:


IF 十六进制值 "文本" - 如果域等于十六进制值,则将文本作为输出字符串;

TRYASCII - 如果域是一个指向ASCII串的指针,则显示这个串;

TRYUNICODE - 如果域是一个指向UNICODE串的指针,则显示这个串;

TRYORDINAL - 如果域是一序号(有16位均为0),则会显示为序号(“#”后跟整数);

OTHERWISE - 如果前面IF语句为真,则停止解码,否则继续解码;


ELSEINT - 如果前面所有的 IF 和 TRYxxx 语句均失败,则以带符号十进制数形式(带小数点)显示这个域;

ELSEHEX -如果前面所有的 IF 和 TRYxxx 语句均为失败,则以十六进制形式显示这个域;

ELSECHAR -如果前面所有的 IF 和 TRYxxx 语句均为失败,则以 ASCII 字符形式显示这个域;

ELSEWCHAR -如果前面所有的 IF 和 TRYxxx 语句均为失败,则以 UNICODE 字符形式显示这个域。

如果域是一个二进制位集,则可以使用BIT序列,如果必要的话可以后面跟关键字 BITZ 与 BITHEX :

BIT 十六进制掩码 "文本" - 如果值与十六进制掩码按位与(AND)的结果不是0,则将文本做为输出串;

BITZ十六进制掩码 "文本" - 如果值与十六进制掩码按位与(AND)的结果是0,则将文本做为输出串;

BITHEX十六进制掩码 - 如果值与十六进制掩码按位与(AND)的结果不是0,则将结果以十六进制形式显示。

特殊关键字 PURGE 会从输出串尾部移除以下几种符号:


空格     ' '
逗号     ','
或     '|'
冒号     ':'
等于     '='
这会让某些解码情况变的简单。关键字END是类型定义结尾标记并会自动运行PURGE命令。


预编译类型

OllyDbg在预编译资源时,已经包含150多种类型描述。以下列出了一部分。您可以在自定义文件中直接使用这些类型:


LANG_X - 操作系统语言ID(0 - 未知、 9 - 语言、 C - 法语,等等)

GENERIC_X - 访问类型(GENERIC_READ, GENERIC_WRITE...)

FILE_SHARE_X - 共享类型(FILE_SHARE_READ, FILE_SHARE_WRITE)

CREATEFILE_X - 文件创建模式(CREATE_NEW, OPEN_EXISTING...)

FILE_ATTRIBUTE_X - 文件属性(READONLY, SYSTEM, DELETE_ON_CLOSE...)

RT_AXX - 资源类型(RT_CURSOR, RT_GROUP_ICON, ASCII string...)

RT_WXX - 资源类型(RT_CURSOR, RT_GROUP_ICON, UNICODE string...)

COORD - 坐标结构 "(X=xxx,Y=yyy)"

STD_IO_X - 标准句柄(STD_INPUT_HANDLE, STD_ERROR_HANDLE...)

GMEM_X - 全局内存类型(GMEM_FIXED, GPTR...)

LMEM_X - 局部内存类型(LMEM_FIXED, LPTR...)

FSEEK_X - 文件查找类型(FILE_BEGIN, FILE_CURRENT...)

OF_X - 文件模式(fOF_READ, OF_SHARE_COMPAT, OF_VERIFY...)

O_X - 文件创建模式(O_RDONLY, O_BINARY, SH_COMPAT...)

SEMAPHORE_X - 信号量类型(SEMAPHORE_ALL_ACCESS, SYNCHRONIZE...)

SLEEP_TIMEOUT - 超时(INFINITE 或时间)

ROP - 一些标准柵格运算标志代码(ROP)(SRCCOPY, MERGEPAINT...)

COLORREF - RGB 颜色值("<WHITE>", "RGB(rr.,gg.,bb.)"...)

WS_X - 窗口风格(WS_OVERLAPPED, WS_POPUP...)

WS_EX_X - 扩展窗口风格(WS_EX_DLGMODALFRAME, WS_EX_TOPMOST...)

MF_X - 菜单标志(MF_BYPOSITION, MF_ENABLED...)

WM_X - ASCII窗口消息类型(WM_CREATE, WM_KILLFOCUS, CB_SETCURSEL...)

WM_W - UNICODE窗口消息类型(WM_CREATE, WM_KILLFOCUS, CB_SETCURSEL...)

VK_X - 虚拟键盘代码(VK_LBUTTON, VK_TAB, VK_F10...)

MB_X - message box style (MB_OK, MB_ICONHAND...)

HKEY_X - 预定义注册表句柄(HKEY_CLASSES_ROOT, HKEY_LOCAL_MACHINE...)

还有更多的预编译类型。如果常量在它文件被定义为ABC_ xxxxxxxx,那么一般就有ABC_X预编译类型。


注意:
1.如果OllyDbg是即时调试器,并且在Windows 95下挂接执行了DebugBreak() 的应用程序,则这个应用程序在挂接后,还会运行。在基于NT的系统下,应用程序应该会在
DebugBreak()暂停。
2. 命令 SMSW (保存机器状态字[Store Machine Status Word])。 这个命令仅接受寄存器 AX 作为参数,而程序编译成EAX接受参数。




您需要登录后才可以回帖 登录 | 立即注册

快速回复 返回顶部 返回列表