:这部分介绍
立即发现调试支持
,主要介绍是调试的页堆(DPH
,Debug Page Heap
),理解了DPH
只有这样,我们才能理解别人写的类似方面的文章,最常用的调试方法基本上与页堆有关:滞后调试支持如下:点击查看
:简单的理解是在堆块后面添加专门用于检测的栅栏页(
fense page
),一旦用户数据区溢出并触发栅栏页面(fense page
)它会立即触发异常,而不是等到下调用堆函数发现问题,会立即发现问题;也可以在堆前添加围栏页
文章目录
- 1.页堆(DPH,完全页堆)
-
- step 一、页堆初步印象
- step 2:整体结构
-
- part 1:第一个内存页面(`4KB`)
- part 2:2-5内存页面(`16KB`)
- step 3.启动和观察页堆
- step 4:堆块结构
- step 5:检测溢出
- 2.准页堆(常规页堆)
-
- 区别1:启动
- 区别2:是否有ENABLE_PAGE_HEAP?
- 区别3:堆是从普通堆,页堆分配?
- 区别4:准页堆能否及时发现溢出?
- 区别5:完整页堆和普通页堆填充模式?
- 3.参考
1.页堆(DPH,完全页堆)
如果只看原理,页堆的内容真的很枯燥,不讲原理,先说明页堆对程序的影响和查看方法,先对页堆有初步的印象。…
step 一、页堆初步印象
页堆:将堆的全部或部分调用重定向
到另一个堆管理器,将申请内存放端放置内存
,且将毗邻
下一页设置为只读
,用这个只读页检查缓冲区溢出;保护页(或栅栏页)也可以放在堆前;页面堆积在内存上会造成巨大的开支,所以默认是关闭的
注:在学习页堆的内部结构之前,用一个简单的例子观察它
启动
和关闭页
堆前后提交大小的变化,
-
1.运行notepad.exe,启动任务管理器记录notepad实例的PID和提交大小
-
2.启动页堆
管理员权限运行gflags.exe
(使用方法,点击此处查看),GUI界面上给notepad.exe打开页堆,命令行也可以
- 3.新运行一个notepad实例(
页堆被启用
)
观察页堆启动和关闭前后提交的大小如下,打开DPH
之后,由于页堆提供了额外的内存分配,提交的大小明显增加
- 4.使用
WMMap
详细查看额外内存分配
- 5.
WinDbg
检查页堆,发现堆结构发生了变化
#查看私有堆的详细信息,-h句柄指定在后面 0:000> !heap -p -h 5ee0000 #5ee一万是页堆的句柄 _DPH_HEAP_ROOT @ 5ee1000 #_DPH_HEAP_ROOT结构地址,第2个页 Freed and decommitted blocks #释放和归还系统的块列表 DPH_HEAP_BLOCK : VirtAddr VirtSize Busy allocations · #占用块 DPH_HEAP_BLOCK : UserAddr UserSize - VirtAddr VirtSize _HEAP @ 5e40000 #普通堆句柄,即HEAP结构的地址 No FrontEnd _HEAP_SEGMENT @ 5e40000 #_HEAP_SEGMENT结构地址 CommittedRange @ 5e404a8 #已提交区域的起始地址 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state ##普通堆上的堆块列表 * 05e404a8 0021 0000 [00] 05e404b0 00107 - (busy) 05e405b0 0146 0021 [00] 05e405b8 00a28 - (free) * 05e40fe0 0004 0146 [00] 05e40fe8 00018 - (busy)
VirtualAllocdBlocks @ 5e4009c
#上面看不懂也没关系,只要知道页堆创建后实际上是有2个堆就行
#1.页堆(句柄是5ee0000),且页堆的句柄就是整个堆的句柄
#2.一个附属的普通堆(句柄是5e40000)
扩展命令
# !heap -p
Dumps addresses of all full page heaps created in the process.
# !heap -p -h ADDRESS-OF-HEAP
Full dump of full page heap at ADDRESS-OF-HEAP.
# !heap -p -a ADDRESS
Tries to figure out if there is a heap block at ADDRESS. This value does not need to be
the address of the start of the block. The command is useful if there is no clue whatsoever
about the nature of a memory area.
# 命令 !heap -p -a 会找出堆块特征
0:000> !heap -p -a 8165FF0
address 08165ff0 found in
_DPH_HEAP_ROOT @ 8161000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
8161f70: 8165000 2000
如果启用了页堆,也可以使用!avrf -hp length
(avf使用方法,点击这里查看)来查看展示length长度记录的堆函数的栈回溯(能跟踪函数HeapAlloc
, HeapReAlloc
, 和HeapFree
)
# 注意:没有开启页堆,命令会失败
0:000> !avrf -hp 2
Application verifier is not enabled for this process.
Use appverif.exe tool to enable it.
下面是使用应用程序验证器开启heap相关测试后的截图,详细分析如下:
- 1.页堆影响
dv
查看局部变量可以看到p1、p2和p3之间相差都是0x2000(8KB
);因申请的内存不会超过4KB
,加上保护页4KB
大小,所以正好是8KB
大小
- 2.堆函数的栈回溯分析
!avrf -hp 2
会显示出2条记录;第一条记录HeapFree: 8165FF8 5 8160000
中显示的3个数字,代表HeapFree(hHeap, 0, p1);
中堆句柄hHeap(8160000
)和堆块信息(用户区首地址p1=8165FF8
,用户申请的长度是5
)
step 2:整体结构
有了整体印象后,下面开始学习一下页堆的原理(数据结构)吧,以x86
为例,一个页的大小是4KB
,页堆的空间大小是以内存页为单位的,下面是页堆的整体结构示意图
对上面的页堆示意图进行简要的说明
part 1:第1个内存页(4KB
)
模拟普通堆的HEAP
结构(多余空间用0xeeeeeeee
填充),属性是只读
,可以检测应用程序意外写HEAP
结构的错误
#1.查看第一个内存页
0:000> dd 5ee0000 l1024 #5ee0000是整个堆的句柄,也是页堆句柄
05ee0000 eeeeeeee eeeeeeee eeeeeeee eeeeeeee #多余空间用0xeeeeeeee填充
05ee0010 eeeeeeee eeeeeeee eeeeeeee eeeeeeee
05ee0020 eeeeeeee eeeeeeee eeeeeeee eeeeeeee
05ee0030 eeeeeeee eeeeeeee eeeeeeee eeeeeeee
05ee0040 01001002 01001002 eeeeeeee eeeeeeee
...
05ee0ff0 eeeeeeee eeeeeeee eeeeeeee eeeeeeee
#2.查看第1个页的属性
0:000> !address 5ee0000
Usage: PageHeap #页堆
Base Address: 05ee0000
Region Size: 00001000 ( 4.000 kB) #一个内存页
Protect: 00000002 PAGE_READONLY #只读
#3.查看模拟的ntdll!_HEAP结构
0:000> ?? sizeof(ntdll!_HEAP)
unsigned int 0x258
0:000> dt ntdll!_HEAP 5ee0000
+0x040 Flags : 0x1001002
+0x044 ForceFlags : 0x1001002
part 2:第2-5个内存页(16KB
)
DPH_HEAP_ROOT
结构(主要用于记录DPH
信息)+ 堆块节点池(node pool
)
DPH_HEAP_ROOT
结构
签名固定是0xffeeddcc
,普通堆的签名是0xeeffeeff
,NormalHeap
记录了附属普通堆的句柄
0:000> ?? sizeof(ntdll!_DPH_HEAP_ROOT)
unsigned int 0xc0
#1.查看_DPH_HEAP_ROOT结构
0:000> dt ntdll!_DPH_HEAP_ROOT 5ee1000
+0x000 Signature : 0xffeeddcc #页堆的固定签名
+0x004 HeapFlags : 0x1002
+0x008 HeapCritSect : 0x05ee16cc _RTL_CRITICAL_SECTION #存放同步用的关键区对象
+0x020 BusyNodesTable : _RTL_AVL_TABLE
+0x05c nBusyAllocations : 1
+0x09c nNodePools : 1 #节点池
+0x0a0 nNodePoolBytes : 0x5ee2000
+0x0b4 NormalHeap : 0x05e40000 Void #附属普通堆的句柄,重要
+0x0b8 CreateStackTrace : 0x04ae456c _RTL_TRACE_BLOCK #UST堆回溯
#2.UST堆回溯的使用,地址是0x04ae456c,将每个地址都按照符号进行解析
0:000> dds 0x04ae456c
04ae456c 00000000
04ae4570 00004001
04ae4574 00090000
04ae4578 7ac6a5f7 verifier!AVrfDebugPageHeapCreate+0x4e7
04ae457c 775daf57 ntdll!RtlCreateHeap+0x44557
04ae4580 76dc0f55 KERNELBASE!HeapCreate+0x45
04ae4584 0057823f FreCheck!main+0x2f [c:\...\frecheck.cpp @ 13]
- 堆块节点池(
node pool
)
用来存储堆块的节点,主要是为了防止堆块的控制信息被覆盖而为每个堆块记录了一个DPH_HEAP_BLOCK
结构(DPH节点)
大小:4个内存页大小 - sizeof(DPH_HEAP_ROOT结构)
DPH_HEAP_BLOCK
:维护堆块的相关信息
#1.查看第2个页的属性
0:000> !address 5ee1000
Usage: PageHeap
Base Address: 05ee1000
Region Size: 00001000 ( 4.000 kB) #1个内存页
State: 00001000 MEM_COMMIT #已经提交
Protect: 00000004 PAGE_READWRITE
Allocation Base: 05ee0000 #第一个内存页地址,也是整个页堆句柄
#2.查看第3、4、5个页的属性
0:000> !address 5ee2000
Usage: PageHeap
Base Address: 05ee2000
Region Size: 00003000 ( 12.000 kB) #3个内存页
State: 00002000 MEM_RESERVE #预留
Protect: <info not present at the target>
Type: 00020000 MEM_PRIVATE
说明:页的state属性不同
通过一份明显会产生异常的代码,讲解页堆的堆常规堆结构的影响,测试用的源码(编译成TestDPH.exe):htc
示例用的也是同一套源码,后面会发现页堆的分析结果会更精确
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
char * p;
HANDLE hHeap = HeapCreate(0, 1024, 0); //创建私有堆
p = (char*)HeapAlloc(hHeap, 0, 9); //在堆上申请了9个bytes的内存(堆上实际消耗40 bytes)
for(int i = 0;i < 20; i++) //溢出,20-9=11个字节,溢出还在本堆块内
*(p + i) = i;
if(!HeapFree(hHeap, 0, p)) //会出现异常,因为释放时发现堆块附加固定内容(若干个ab)被修改
printf("Free %x from %x failed.", p, hHeap);
if(!HeapDestroy(hHeap))
printf("Destroy heap %x failed.", hHeap);
printf("Exit with 0");
return 0;
}
step 3:启动和观察页堆
下面学习一下启动页堆和查看页堆,启动很多种方式(上面也有介绍)
- 1.启动DPH功能
可以全局启动,也可以针对某一个进程启动,以启动TestDPH.exe为例
#命令行中输入下面命令启动
gflags /p /enable TestDPH.exe /full 或者 gflags /i TestDPH.exe +hp
#细节:取消某一个进程的DPH功能,最稳妥的方法就是直接删除注册表里的项
上面命令会在注册表里创建子键\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\testdph.exe
,并加入2个键值:
PageHeapFlags
:2
代表常规DPH
,3
代表完全DPH
;自己测试发现,PageHeapFlags
只会影响gflags /p
,如果想要开启常规DPH,要多加一个VerifierFlags=0x8000
的键值
#PageHeapFlags参数
C:\WINDOWS\system32>gflags /p
path: SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
checkesp.exe: page heap enabled with flags (full traces ) #PageHeapFlags=3,设置时指定full就是完全DPH
testdph.exe: page heap enabled with flags (traces ) #PageHeapFlags=2,常规DPH
- 2.观察页堆
WinDbg打开TestDPH.exe,输入!gflag
0:000> !gflag
Current NtGlobalFlag contents: 0x02000000
hpa - Place heap allocations at ends of pages #DPH启动
#1.执行完HeapCreate(0, 1024, 0),此时堆私有堆已经创建完成,查看堆的句柄
0:000> dv
hHeap = 0x05ee000
0:000> !heap -p #参数p用来显示页堆的
Active GlobalFlag bits:
hpa - Place heap allocations at ends of pages
StackTraceDataBase @ 04ad0000 of size 01000000 with 0000000d traces
PageHeap enabled with options:
ENABLE_PAGE_HEAP COLLECT_STACK_TRACES
active heaps:
+ 48f0000 #页堆,+后面是页堆的句柄
ENABLE_PAGE_HEAP COLLECT_STACK_TRACES
NormalHeap - 5ae0000 #每个页堆还附带一个普通堆
HEAP_GROWABLE
...
+ 5ee0000 #私有堆的句柄,HeapCreate返回值
ENABLE_PAGE_HEAP COLLECT_STACK_TRACES
NormalHeap - 5e40000
HEAP_GROWABLE HEAP_CLASS_1
#2.不加参数p只显示普通堆
0:000> !heap
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
Index Address Name Debugging options enabled
1: 05ae0000 #普通堆的句柄
2: 05ed0000
3: 05ff0000
4: 05e40000
#3.查看私有堆的详细信息,-h后面指定的是句柄
0:000> !heap -p -h 5ee0000 #5ee0000是刚创建的私有堆的句柄
_DPH_HEAP_ROOT @ 5ee1000 #_DPH_HEAP_ROOT地址,第2个页
Freed and decommitted blocks #释放和已经归还系统的块列表
DPH_HEAP_BLOCK : VirtAddr VirtSize
Busy allocations · #占用块
DPH_HEAP_BLOCK : UserAddr UserSize - VirtAddr VirtSize
_HEAP @ 5e40000 #普通堆的句柄,即HEAP结构的地址
No FrontEnd
_HEAP_SEGMENT @ 5e40000 #_HEAP_SEGMENT结构地址
CommittedRange @ 5e404a8 #已提交区域的起始地址
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state #普通堆上的堆块列表
* 05e404a8 0021 0000 [00] 05e404b0 00107 - (busy) #管理堆块
05e405b0 0146 0021 [00] 05e405b8 00a28 - (free)
* 05e40fe0 0004 0146 [00] 05e40fe8 00018 - (busy)
VirtualAllocdBlocks @ 5e4009c
#说明:页堆上还没有分配任何用户堆块,普通堆上只有一个管理堆块
step 4:堆块结构
页堆也会分配堆块给用户使用,每个页堆块至少占用2个内存页:用户数据区页 + 用于检测溢出的栅栏页
;为了及时检测到溢出,堆块的数据区的原则进行放置的,下面是页堆提供的堆块的一个示意图
页堆堆块会涉及2个大的部分:
-
1.页堆堆块的数据组成:剩余额外空间()+
DPH_BLOCK_INFORMATION
+ 用户数据区 + 满足分配粒度要求的额外填充字节() -
2.堆块节点池(node pool):会有一个
DPH_HEAP_BLOCK
结构也对页堆中的堆块进行记录
初始值:主要涉及到
_DPH_BLOCK_INFORMATION
、用户申请的数据区初始填充和对齐要填充的数据的初始值
0:000> dt ntdll!_DPH_BLOCK_INFORMATION
+0x000 StartStamp : Uint4B
+0x01c EndStamp : Uint4B
下面是一个页堆堆块填充模式汇总
事项 | 占用堆块 | 空闲堆块 |
---|---|---|
StartStamp | abcdbbbb | abcdbbba |
EndStamp | dcbabbbb | dcbabbba |
用户数据区 | c0(也可以指定00) | f0 |
对齐要填充的数据 | d0 | - |
单步执行完p = (char*)HeapAlloc(hHeap, 0, 9);
会在堆上申请一块内存,下面是使用WinDbg的查看情况
- 1.私有堆建立位置
0:000> dv
hHeap = 0x05ee0000 #私有堆的句柄
p = 0x05ee5ff0 "???" #HeapAlloc(hHeap, 0, 9);申请的堆块的用户区的首地址
0:000> !heap -p #参数p用来显示页堆的
active heaps:
+ 5ee0000 #私有堆是在页堆里建立的,用户区0x05ee5ff0在页堆中
ENABLE_PAGE_HEAP COLLECT_STACK_TRACES
NormalHeap - 5e40000
HEAP_GROWABLE HEAP_CLASS_1
#说明:HeapAlloc的返回值0x05ee5ff0与页堆的2个句柄(5ee0000和5e40000)比较,说明私有堆建立在页堆5ee0000中
- 2.内存中查看栅栏页
#DPH中堆块的用户数据区前面的控制结构是ntdll!_DPH_BLOCK_INFORMATION
0:000> ?? sizeof(ntdll!_DPH_BLOCK_INFORMATION)
unsigned int 0x20
0:000> dt ntdll!_DPH_BLOCK_INFORMATION 0x05ee5ff0-0x20 #地址是用户数据区回退一个_DPH_BLOCK_INFORMATION结构大小
+0x000 StartStamp : 0xabcdbbbb #与EndStamp呼应,固定值
+0x004 Heap : 0x05ee1000 Void #DPH_HEAP_ROOT结构的地址,页堆的第2个页首地址
+0x008 RequestedSize : 9 #HeapAlloc(hHeap, 0, 9);中申请的字节大小
+0x00c ActualSize : 0x1000 #堆块的实际大小(4KB),不包括栅栏页
+0x010 FreeQueue : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x010 FreePushList : _SINGLE_LIST_ENTRY
+0x010 TraceIndex : 0 #UTS中追踪记录的序号
+0x018 StackTrace : 0x04ae459c Void
+0x01c EndStamp : 0xdcbabbbb
#观察申请的堆块后的内存情况
0:000> dd 0x05ee5ff0-0x20-0x10 #HeapAlloc返回值-_DPH_BLOCK_INFORMATION结构大小-0x10
05ee5fc0 00000000 00000000 00000000 00000000 #没有使用的空闲数据
05ee5fd0 abcdbbbb 05ee1000 00000009 00001000 #_DPH_BLOCK_INFORMATION结构
05ee5fe0 00000000 00000000 04ae459c dcbabbbb
05ee5ff0 c0c0c0c0 c0c0c0c0 d0d0d0c0 d0d0d0d0 #前9个是HeapAlloc申请空间,默认填充c0,后面是补齐数据
05ee6000 ???????? ???????? ???????? ???????? #栅栏页,不可访问属性,显示问号
05ee6010 ???????? ???????? ???????? ????????
#查看私有堆的详细信息
0:000> !heap -p -h 5ee0000
_DPH_HEAP_ROOT @ 5ee1000 #页堆的第二个页
Freed and decommitted blocks
DPH_HEAP_BLOCK : VirtAddr VirtSize
Busy allocations
DPH_HEAP_BLOCK : UserAddr UserSize - VirtAddr VirtSize
05ee1f70 : 05ee5ff0 00000009 - 05ee5000 00002000 #新申请的9个字节所在的位置(再次印证堆块在页堆上)
unknown!fillpattern
_HEAP @ 5e40000 #普通堆
No FrontEnd
_HEAP_SEGMENT @ 5e40000
CommittedRange @ 5e404a8
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
* 05e404a8 0021 0000 [00] 05e404b0 00107 - (busy)
05e405b0 0146 0021 [00] 05e405b8 00a28 - (free)
* 05e40fe0 0004 0146 [00] 05e40fe8 00018 - (busy)
VirtualAllocdBlocks @ 5e4009c
- 3.页堆的属性
#1.内存消耗:程序员申请了9个字节大小,页堆确分配了8KB的虚拟地址 + 一个放在堆块节点池中的DPH_HEAP_BLOCK结构
# HeapAlloc返回值 大小 堆块起始地址 堆块大小
DPH_HEAP_BLOCK : UserAddr UserSize - VirtAddr VirtSize
05ee1f70 : 05ee5ff0 00000009 - 05ee5000 00002000 #新申请的9个字节所在的位置(再次印证堆块在页堆上)
#2.观察栅栏页的属性
0:000> !address 05ee5000+1000 #堆块起始地址+4KB
Usage: PageHeap #用途是页堆,根据使用用于,会显示Stack等用途
Base Address: 05ee6000
Region Size: 000fa000 (1000.000 kB)
State: 00002000 MEM_RESERVE #预留
Protect: <info not present at the target>
Allocation Base: 05ee0000 #页堆的句柄
Allocation Protect: 00000001 PAGE_NOACCESS
More info: !heap -p 0x5ee1000 #0x5ee1000是_DPH_HEAP_ROOT结构的地址
#3.观察DPH_HEAP_BLOCK结构信息
0:000> dt ntdll!_DPH_HEAP_BLOCK 05ee1f70
+0x000 pNextAlloc : 0x05ee1020 _DPH_HEAP_BLOCK
+0x000 AvailableEntry : _LIST_ENTRY [ 0x5ee1020 - 0x0 ]
+0x000 TableLinks : _RTL_BALANCED_LINKS
+0x010 pUserAllocation : 0x05ee5ff0 "???" #用户数据区起始地址
+0x014 pVirtualBlock : 0x05ee5000 "???" #堆块所在内存页的起始地址
+0x018 nVirtualBlockSize : 0x2000 #堆块的总空间大小(8KB,2个内存页)
+0x01c nVirtualAccessSize : 0x20 #可访问区域大小(64bytes)
+0x020 nUserRequestedSize : 9
+0x024 nUserActualSize : 0x5ee1f60
+0x028 UserValue : 0x05ee1fc8 Void
+0x02c UserFlags : 0xfd5c
+0x030 StackTrace : 0x04ae459c _RTL_TRACE_BLOCK #栈回溯信息
#4.d*s相关命令:将给定范围内存的内容中的每一元素都当作符号进行解析
# dds:4字节当作一个符号,`dqs:4字节当作一个符号,dqs:按照处理器位数自动选择
0:000> dds 0x04ae459c
04ae459c 00000000
04ae45a0 00005001
04ae45a4 000b0000
04ae45a8 7ac6ab70 verifier!AVrfDebugPageHeapAllocate+0x240
04ae45ac 776292ab ntdll!RtlDebugAllocateHeap+0x39
04ae45b0 77590aad ntdll!RtlpAllocateHeap+0xed
04ae45b4 7759081a ntdll!RtlpAllocateHeapInternal+0x105a
04ae45b8 7758f7ae ntdll!RtlAllocateHeap+0x3e
04ae45bc 00578259 testHEAP!main+0x49 [c:\...\frecheck.cpp @ 14]
step 5:检测溢出
直接输入g
让目标程序运行,出现访问异常,下面分析结果显示;堆溢出覆盖到栅栏页直接出现异常,立刻发现溢出
,而不是等到下次调用堆函数才是否发现溢出()
0:000> g (102a0.fd5c): Access violation - code c0000005 (first chance) eax=05ee6000 ebx=00faf000 ecx=01001010 edx=00000000 esi=00d9fb80 edi=00d9fc70 eip=00578284 esp=00d9fb80 ebp=00d9fc70 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 FreCheck!main+0x74: 00578284 8808 mov byte ptr [eax],cl ds:002b:05ee6000=?? #操作的是eax #说明:导致访问异常的指令中,eax=05ee6000,这正是栅栏页的首地址,异常指令的地址是00578284 #1.查看异常指定地址00578284前后的反汇编的代码(for(int i = 0;i < 20; i++) *(p + i) = i;) 0057826c 8b45e0 mov eax,dword ptr [ebp-20h] #ebp-20h里面存的应该是i 0057826f 83c001 add eax,1 00578272 8945e0 mov dword ptr [ebp-20h],eax 00578275 837de014 cmp dword ptr [ebp-20h],14h #与20进行比较 00578279 7d0d jge FreCheck!main+0x78 (00578288) 0057827b 8b45f8 mov eax,dword ptr [ebp-8] #4条语句实现*(p + i) = i,ebp-8的是目的 0057827e 0345e0 add eax,dword ptr [ebp-20h] 00578281 8a4de0 mov cl,byte ptr [ebp-20h] 00578284 8808 mov byte ptr [eax],cl ds:002b:05ee6000=?? #异常指令 00578286 ebe4 jmp FreCheck!main+0x5c (0057826c) #进行下一次for循环 00578288 8bf4 mov esi,esp #for循环不符合条件后执行的第一条语句 0057828a 8b45f8 mov eax,dword ptr [ebp-8] 0057828d 50 push eax 0057828e 6a00 push 0 00578290 8b4dec mov ecx,dword ptr [ebp-14h] 00578293 51