飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 2390|回复: 2

[转贴] PE 文件格式启发式学习2(以count.dll为例)

[复制链接]
  • TA的每日心情
    慵懒
    2024-3-21 21:06
  • 签到天数: 1489 天

    [LV.10]以坛为家III

    发表于 2008-6-2 09:19:28 | 显示全部楼层 |阅读模式
    原贴:http://bbs.pediy.com/showthread.php?t=65620

    原贴中还有楼主的答疑,值得关注!


    引言: 上一次以hello.exe 为例,介绍了pe 文件头,节表和导入表。
      这一次我们以 count.dll 为例,介绍导出表和重定位表
      count.dll 是罗云斌win32汇编编程中的例子程序,因其短小,故被选中。
      
    问5.1:dll 为什么叫动态连接库,与平常的静态连接有什么不同。
    答5.1:静态连接库在编译连接时由link 程序把库文件直接添加到运行程序中。
        动态连接库在编译连接时只是把插桩加到代码里。运行时由加载器载入
        内存,修改插桩代码使指向正确的地址。这个过程在上一讲中已经说过了。
       
    问5.2:既然是库函数,就会有一堆函数构成,那么是否每个函数都可以被外边调用呢?
    答5.2:库函数可分为三类,一个是库入口函数。
      一类为可被外部调用,叫导出库函数。
      一类不能被外部调用,我们叫它私有函数吧。因为它没有向外提供接口。
      
    问5.3:导出函数是怎样向外提供接口的呢? 或者说我们怎样才能使用导出函数呢?
    答5.3: 这个问题是我们的重点,我们结合实例来吧,慢慢把它讲清楚。
      用ultraedit 打开这个dll 文件。
      ultraedit看到的是最原始的文件,其它众多的pe 分析软件都是从原始文件分析得到的。哦,当然,这话说的多余了。
      大概浏览一下这个文件。
      区分一下dos头, PE 头, 节表, 有几个块组成。这些都是很明显的。上一讲中已经说过了。
      哦,上一讲讲的是exe, 这里是dll, 不过它们都是PE 文件, 格式是一样的。
      
    顺便复习一下上次内容,这可以说是上次4讲的精华了,却是以count.dll 为例,同样通俗易懂:
      dos 头: 标记 "MZ"
    00000000   4D 5A 90 00 03 00 00 00  04 00 00 00 FF FF 00 00   MZ?........????..
    00000010   B8 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00   ?......@.......
    00000020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
    00000030   00 00 00 00 00 00 00 00  00 00 00 00 C0 00 00 00   ............?..
      PE 头: 标记 "PE"
    000000C0   50 45 00 00 4C 01 04 00  F6 34 EB 3C 00 00 00 00   PE..L...??....
    000000D0   00 00 00 00 E0 00 0E 21  0B 01 05 0C 00 02 00 00   ....?.!........  
    从dos头 0x3c 处也能看出PE 头位置。
      节表: 有明显的字符串标记,此处是".text"
    000001B0                            2E 74 65 78 74 00 00 00           .text...
    000001C0   70 00 00 00 00 10 00 00  00 02 00 00 00 04 00 00   p...............
    000001D0   00 00 00 00 00 00 00 00  00 00 00 00 20 00 00 60   ............ ..`
    000001E0   2E 72 64 61 74 61 00 00  BC 00 00 00 00 20 00 00   .rdata..?... ..
    000001F0   00 02 00 00 00 06 00 00  00 00 00 00 00 00 00 00   ................
    00000200   00 00 00 00 40 00 00 40  2E 64 61 74 61 00 00 00   ....@..@.data...
    00000210   04 00 00 00 00 30 00 00  00 00 00 00 00 00 00 00   .....0..........
    00000220   00 00 00 00 00 00 00 00  00 00 00 00 40 00 00 C0   ............@..?
    00000230   2E 72 65 6C 6F 63 00 00  2C 00 00 00 00 40 00 00   .reloc..,....@..
    00000240   00 02 00 00 00 08 00 00  00 00 00 00 00 00 00 00   ................
    00000250   00 00 00 00 40 00 00 42                            [email=....@..B]....@..B[/email]........   
      数一数节表有4个
      从PE 头 0xc7处也能看出来。
      大致浏览一下后面的数据块划分,块与块之间很容易识别,因为每一块之间都有很多0,
      它们是以512字节对齐填充的。
      咦! 怎么只看到了3块, 节表头中不是说4块吗?
      再仔细对照一下节表头:跟我一块找找。
    00000400   55 8B EC B8 01 00 00 00  C9 C2 0C 00 55 8B EC 6A   U嬱?...陕..U嬱j
    00000410   01 FF 75 10 FF 75 0C FF  75 08 E8 4B 00 00 00 C9   .??u.??u.??u.鐺...?
    00000420   C2 0C 00 55 8B EC FF 05  00 30 00 10 FF 35 00 30   ?.U嬱??..0..??5.0
    00000430   00 10 FF 75 0C FF 75 08  E8 CF FF FF FF A1 00 30   ..??u.??u.柘???????0
    00000440   00 10 C9 C2 08 00 55 8B  EC FF 0D 00 30 00 10 FF   ..陕..U嬱??..0..??
    00000450   35 00 30 00 10 FF 75 0C  FF 75 08 E8 AC FF FF FF   5.0..??u.??u.璎??????
    00000460   A1 00 30 00 10 C9 C2 08  00 CC FF 25 00 20 00 10   ?0..陕..?%. ..
    00000470   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................  
    这段是 text 段,因为节表已经说了,text 段内存地址0x1000,大小0x70
    在文件中处于偏移0x400, 占用文件大小0x200 字节。

    00000600   38 20 00 00 00 00 00 00  30 20 00 00 00 00 00 00   8 ......0 ......
    00000610   00 00 00 00 48 20 00 00  00 20 00 00 00 00 00 00   ....H ... ......
    00000620   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
    00000630   38 20 00 00 00 00 00 00  27 02 53 65 74 44 6C 67   8 ......'.SetDlg
    00000640   49 74 65 6D 49 6E 74 00  55 53 45 52 33 32 2E 64   ItemInt.USER32.d
    00000650   6C 6C 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ll..............
    00000660   00 00 00 00 F6 34 EB 3C  00 00 00 00 9C 20 00 00   ....??....?..
    00000670   01 00 00 00 02 00 00 00  02 00 00 00 88 20 00 00   ............?..
    00000680   90 20 00 00 98 20 00 00  46 10 00 00 23 10 00 00   ?..?..F...#...
    00000690   A8 20 00 00 B2 20 00 00  00 00 01 00 43 6F 75 6E   ?..?......Coun
    000006A0   74 65 72 2E 64 6C 6C 00  5F 44 65 63 43 6F 75 6E   ter.dll._DecCoun
    000006B0   74 00 5F 49 6E 63 43 6F  75 6E 74 00 00 00 00 00   t._IncCount.....
    000006C0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................  
    这段是 rdata 段,因为节表已经说了,rdata 段内存地址0x2000,大小0xbc
    在文件中处于偏移0x600, 占用文件大小0x200 字节。


    00000800   00 10 00 00 18 00 00 00  28 30 2E 30 3E 30 4B 30   ........(0.0>0K0
    00000810   51 30 61 30 6C 30 00 00  00 00 00 00 00 00 00 00   Q0a0l0..........
    00000820   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
    这段是 reloc 段,因为节表已经说了,reloc 段内存地址0x4000,大小0x20
    在文件中处于偏移0x800, 占用文件大小0x200 字节。

    哦!也!文件分析完了。
    呦,不是说看看丢了哪一块吗? 是data 段丢了。看看节表怎么说:
    节表已经说了,data 段内存地址0x4000,大小0x4
    在文件中处于偏移0x0, 占用文件大小0x0 字节。
    怪不得文件中找不到它的踪影,原来它不存在。但内存中还是给它留了位置。
    不过这里是个特例,一般文件都会有data 段的存在。

    (小声说)别高兴太早了,这只是划分了各个块,把每块的具体内容分析完才算完
    哦,也.
    下面我们再详细分析一下各个段功能。
    text 段为核心,其它段都是为它服务的。
    text 段
    即代码段,由指令集构成。你可以反汇编出这部分内容,就知道它们的功能了。
    为了本帖的完整性,我把它贴过来,并不长。
    10001000                           EntryPoint:
    10001000  55                            push  ebp
    10001001  8BEC                          mov  ebp,esp
    10001003  B801000000                    mov  eax,00000001h
    10001008  C9                            leave
    10001009  C20C00                        retn  000Ch
    ;----------------------------------------------------------------------------------------------------
    1000100C                           SUB_L1000100C:
    1000100C  55                            push  ebp
    1000100D  8BEC                          mov  ebp,esp
    1000100F  6A01                          push  00000001h
    10001011  FF7510                        push  [ebp+10h]
    10001014  FF750C                        push  [ebp+0Ch]
    10001017  FF7508                        push  [ebp+08h]
    1000101A  E84B000000                    call  jmp_USER32.dll!SetDlgItemInt
    1000101F  C9                            leave
    10001020  C20C00                        retn  000Ch
    ;----------------------------------------------------------------------------------------------------
    10001023                           _IncCount:
    10001023  55                            push  ebp
    10001024  8BEC                          mov  ebp,esp
    10001026  FF0500300010                  inc  [L10003000]
    1000102C  FF3500300010                  push  [L10003000]
    10001032  FF750C                        push  [ebp+0Ch]
    10001035  FF7508                        push  [ebp+08h]
    10001038  E8CFFFFFFF                    call  SUB_L1000100C
    1000103D  A100300010                    mov  eax,[L10003000]
    10001042  C9                            leave
    10001043  C20800                        retn  0008h
    ;----------------------------------------------------------------------------------------------------
    10001046                           _DecCount:
    10001046  55                            push  ebp
    10001047  8BEC                          mov  ebp,esp
    10001049  FF0D00300010                  dec  [L10003000]
    1000104F  FF3500300010                  push  [L10003000]
    10001055  FF750C                        push  [ebp+0Ch]
    10001058  FF7508                        push  [ebp+08h]
    1000105B  E8ACFFFFFF                    call  SUB_L1000100C
    10001060  A100300010                    mov  eax,[L10003000]
    10001065  C9                            leave
    10001066  C20800                        retn  0008h
    ;----------------------------------------------------------------------------------------------------
    10001069  CC                            Align  2
    1000106A                           jmp_USER32.dll!SetDlgItemInt:
    1000106A  FF2500200010                  jmp  [********]  //故意把名字隐含了
    ;----------------------------------------------------------------------------------------------------
    代码分三类:
      第一类与地址无关,它们二进制代码已经定下来了
      第二类与地址有关,它们二进制代码也定下来了,如果dll 加载到它的默认地址,代码不用修改
      第三类是代码还没有确定,用插桩来表示。如:
      jmp  [********], 它的插桩是 10002000 地址

    先来解决插桩问题吧,这就是hello.exe 讲座中的导入表问题。
    1. 内存地址 10002000-10000000(image_base) = 2000(RVA)
       说实话,image_base 我记不清位置,也没有明显标记,每次要查结构偏移。
       好在dll 通常是10000000,exe 通常是400000,不查也没有问题。
       我又查了一边,偏移是PE 标识后(不包括PE00标识)第13 个DWORD 偏移处。加深点印象,跟IAT在目录项偏移一样
       
    2. RVA -> offset: 从节表知 RVA 0x2000== offset 0x600:
    3. [0x600] == 2038,  RVA 2038==offset 638, [0x638]== "0207 setDlgItemInt", 前面是导出序号,后面是导出名称
    ; ---------------------------------------------------------------------------
    loader 解决插桩的问题是从导入表开始的。导入表是目录项的第二项:
    00000140   08 20 00 00 28 00 00 00                            . ..(...

    导入表指向导入函数库数组。
    RVA 2008 == offset 608, length=0x28 (一个导入表结构为5个DWORD-0x14, 故0x28为两项,一个有效项,一个全0尾标识)
    第一项:
    originFirstThunk == 2030, RVA 2030==offset 630 (IAT 的备份,供你看的)
    Name = 2048,  RVA 2048 = offset 648 == "USER32.dll"
    FirstThunk == 2000, RVA 2000 == offset 600 (IAT loader 加载时会更改这一部分,以完成插桩)
    导入表给出了"USER32.dll",插桩处2038 给出了函数名"setDlgItemInt", loader 根据这些信息完成插桩。
    ; ---------------------------------------------------------------------------
    至此我们已经复习了hello.exe 中讲过的东西了。

    问5.4:其实话说的越多越不清楚,越少越容易扼要,把导入表部分再概括一下把。
    答5.4:导入表在rdata 域。
      第一部分为导入地址表,这部分loader 在加载时会修改其数值完成插桩。
      第二部分为导入表。导入表是为IAT 服务的,loader 要修改IAT, 必须要知道导入函数的名称。
      这由导入表提供。导入表同时还提供,该函数名负责IAT 表中哪一个区间。
      所以,导入表是导入函数库数组。每个结构有5个DWORD 变量,2个没用。
      3个变量全是RVA, 一个指向函数名,一个指向IAT, 另一个也指向IAT,在结构最前面,但这个IAT loader 不会更改它。
      第三部分按地理位置划分是IAT 的备份表,就是导入表中Origin_FirstThunk指向的部分。
      第四部分是导入函数名表,前面是序号导出,后面紧跟名称导出。修改插桩当然要用这些信息
      第五部分为函数库名称区域。因为导入表只提供RVA, RVA指向的是这个区域。
       
    问5.5:好,这样一看rdata 的上半部分意义就明白了,那下面还有一部分内容呢?
    答5.5:这就是还没开始讲的导出表了。导出表比导入表简单。因为导入表可能从好几个库中导入,
      而导出表只是将自己的相关函数导出。
      
    问5.6:为什么要把导出表和导入表放到一起呢。
    答5.6:因为它们的属性相同。看节表rdata. 0x40000040,两个属性,我查了一下文档,前面那个1代表可读,
    后面那个1代表代码包含初始化数据。

    问5.7: 具体导出表要包含那些内容才算把函数导出了呢?
    答:呦,拖课了! 今天主要是复习了一下前边讲的内容,下一课我们再讲导出表!


    问5.7: dll 和 exe 文件都是PE 文件, 在PE 文件中是怎样区分的呢?
    答5.7:有一些小差别。
    例如,  exe 通常被加载到0x400000, 而dll 默认加载是0x10000000
      exe 通常不含reloc 段,而dll 包含。
      exe 通常不含export 段, 而dll 包含。
      exe 属性010f, 最后1bit 说它没有重定位信息
      dll 属性210e.  最高位的2 说它是dll, 这个好像是最关键差别了吧。

    问5.8:exe 的 entrypoint 是程序执行的起始点,dll 的 entrypoint 是什么呢?
    答5.8:这个地方是dll 的入口函数地址,它是不可以省略的。
       dll 在加载,卸载以及线程加载,卸载时都会
      到这里执行程序。  它的通用结构是这样的。
    BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpReserved)
    {
      switch(fswReason)
      case DLL_PROCESS_ATTACH
      ......
      case DLL_THREAD_ATTACH
      .....
      case DLL_THREAD_DETACH
      .....
      case DLL_PROCESS_DETACH
      ....
      return (TRUE or False)  // true , 成功,false 失败, loader 会把它从内存卸掉。
    }   
    count.dll 中没有按这种结构,它只是简单的返回一个TRUE,因为它不需要申请内存和释放内存等初始化操作。
    count.dll entrypoint 是10001000, PE 标识后第10 DWORD 地址,记不住用工具查。
      
    问5.9:前面说过dll 有入口函数, 导出函数和非导出函数。又回到上次未讲的问题
      导出表是怎样把函数导出的。
    答5.9:我们先猜一猜导出函数的关键要素吧。
      1. 导出库名称
      2. 函数导出序号。(提供序号导出)
      3. 导出函数名    (提供函数名导出)
      4. 序号或函数名对应的地址。
      它向系统报告这些信息已经足够了。
      
    问5.10: 结合例子和结构定义具体说一下吧。
    答5.10: 看count.dll 目录项第一项
    00000130                            60 20 00 00 5C 00 00 00           ` ..\...  
      位置 RVA 2060 == offset 660
      大小 0x5c
    00000660   00 00 00 00 F6 34 EB 3C  00 00 00 00 9C 20 00 00   ....??....?..
    00000670   01 00 00 00 02 00 00 00  02 00 00 00 88 20 00 00   ............?..
    00000680   90 20 00 00 98 20 00 00  46 10 00 00 23 10 00 00   ?..?..F...#...
    00000690   A8 20 00 00 B2 20 00 00  00 00 01 00 43 6F 75 6E   ?..?......Coun
    000006A0   74 65 72 2E 64 6C 6C 00  5F 44 65 63 43 6F 75 6E   ter.dll._DecCoun
    000006B0   74 00 5F 49 6E 63 43 6F  75 6E 74 00               t._IncCount.  
      
    那么这是一个什么样的数据结构呢?我们先猜猜看,这里也叫逆向学习方法吧。
    首先函数名称:count.dll(offset 69c), 函数名称_DecCount(offset 6a8), _IncCount(6b2) 都已经看到了。
    转换为RVA. offset 69c =RVA 209c
        offset 6a8 =RVA 20a8
        offset 6b2 =RVA 20b2
    很高兴在数据中找到了9c 20, a8 20, b2 20. 关注一下这些地址。
    从660 在滤一下,感觉后面的00000002 可能是个数吧。
    后边的2088, 2090,2098
    RVA 2088 == offset 688 [688] == 46 10 00 00 23 10 00 00 // 像函数地址,赶紧确认一下(在上一篇),正是。
    RVA 2090 == offset 690 [690] == A8 20 00 00 B2 20 00 00 // 函数名称地址
    RVA 2098 == offset 690 [690] == 00 00 01 00    // 像hint
    这样划分后,看不懂的就不多了,学习数据结构是时候了。
    typedef struct _IMAGE_EXPORT_DIRECTORY {
        DWORD   Characteristics;
        DWORD   TimeDateStamp;
        WORD    MajorVersion;
        WORD    MinorVersion;
        DWORD   Name;
        DWORD   Base;
        DWORD   NumberOfFunctions;
        DWORD   NumberOfNames;
        DWORD   AddressOfFunctions;     // RVA from base of image
        DWORD   AddressOfNames;         // RVA from base of image
        DWORD   AddressOfNameOrdinals;  // RVA from base of image
    } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
    我们分析的是对的,那个base 是什么东西 ?是导出函数的基序号。所以导出函数序号不是0,1,而是1,2 了
    很明显,1指的是1046地址,2指的是1023地址
    问5.11 再总结一下导出表吧。
    答5.11 导出表有两种导出方法,一种是按名称导出,一种是按序号导出。其中序号导出的个数总是大于等于名称导出的个数。
    导出的函数地址按4字节一字排开。每一个函数索引号就是+Base 值就是导出序号。
    序号不直观,所以有些函数用名称导出,名称导出最终还是要找到函数序号。所以把名称所在的名称数组的位置为索引
    去从名称序号数组中拿到序号(此为索引号),由索引号取到函数地址。

    问5.12 假如loader 要插桩本函数 _IncCount 地址,它是怎样操作的。
    答5.12
    1. 它首先要加载我们的dll. 用loadlibrary
    2. 找到我们的导出表。
    3. 再找到导出表中AddressOfNames。
    4. 遍历该表找到_IncCount 函数,记下它的索引
    5. 从AddressOfNameOrdinals数组中,取到该索引对应的函数地址序号
    6. 从导出表中找到AddressOfFunctions, 用得到的序号去取到它的地址。
    7. 将该地址去填充到IAT 的对应位置上。

    这样插桩就完成了。哇!这个小插桩要经过这么多步骤哇,有没有办法简化一下啦... 等着你去实现呢!

    问5.13  count.dll 中还有一个reloc 节,讲讲它是怎样构成的。
    答5.13  reloc 也是为text 段服务的,前面说过,若dll 加载到它默认位置,可以不用reloc 段。
    当不能加载到默认地址时, 某些于地址有关的指令需要重新定位。就是说要修改指令中地址
    使其指向正确的地址。
    看目录项中reloc 表,第6个表(索引号为5):
    00000160   00 40 00 00 18 00 00 00                            .@......
    RVA 4000 = offset 800, size=0x18
    哦,纵使不用reloc 目录项,直接目视也看见它了。这个程序很小,是这样的。
    00000800   00 10 00 00 18 00 00 00  28 30 2E 30 3E 30 4B 30   ........(0.0>0K0
    00000810   51 30 61 30 6C 30 00 00  00 00 00 00 00 00 00 00   Q0a0l0..........
    我们也像export 表一样,先猜猜reloc 结构应该有那些重要元素。
    1. 内存地址, 4byte, 我们要知道对哪的指令进行重定位。即where 问题
    2. 替换方法。 是替换一字节,2字节还是4字节, 是how 的问题,估计有几个bit 就够了。
    3. 用什么替换。 是一个what 的问题。 这个问题就不用考虑了,这个what,就是加载地址与默认地址偏移。
    这样看起来一个reloc 项至少也要 5 bytes 了。如果有很多项,那这很多项就构成一个数组。
    我这里介绍的方法是一种逆向的学习方法,或者是原始的思考方法。因为我想最初设计这个PE 结构的人也会
    这么想。
    现在使用的PE 结构在这个想法的基础上进行了优化,使得reloc表占用较少的字节,
    具体说是它让一个reloc 项占用2byte,下面看它的方法:

    1.
    typedef struct _IMAGE_BASE_RELOCATION {
        DWORD   VirtualAddress;
        DWORD   SizeOfBlock;
    } IMAGE_BASE_RELOCATION;

    它的意思是说,它要重定位VirtualAddress=1000这块区域, 该区域所占的重定位信息大小为SizeOfBlock=0x18
    后面紧跟的每2 个bytes 构成一个重定位项, 其中低12 bit为重定位地址, 高4bits 为重定位类型。
    我这里把重定位类型copy 过来,其中有的在x86 上是用不到的。
    //
    // Based relocation types.
    //

    #define IMAGE_REL_BASED_ABSOLUTE              0
    #define IMAGE_REL_BASED_HIGH                  1
    #define IMAGE_REL_BASED_LOW                   2
    #define IMAGE_REL_BASED_HIGHLOW               3
    #define IMAGE_REL_BASED_HIGHADJ               4
    #define IMAGE_REL_BASED_MIPS_JMPADDR          5
    #define IMAGE_REL_BASED_SECTION               6
    #define IMAGE_REL_BASED_REL32                 7

    #define IMAGE_REL_BASED_MIPS_JMPADDR16        9
    #define IMAGE_REL_BASED_IA64_IMM64            9
    #define IMAGE_REL_BASED_DIR64                 10
    #define IMAGE_REL_BASED_HIGH3ADJ              11

    地址只有12为意味着你只能管理4K 范围,是的,如果超过了4K范围,我们重新定义一个IMAGE_BASE_RELOCATION
    变量就可以了,它又能管理下一个4K的范围。后续没16bit 为一个reloc 项, 最尾部以0000 结尾。当然,由于
    重定位块大小有定义,纵使不用0000标识结尾也没有问题,把这里的冗余姑且叫双保险吧,就是浪费了2bytes
    count.dll 中,我们先算算有几个重定位项。哦,不用算,有7个,一查就查出来了。
    但当程序大的时候还是要计算的。(0x18-8)/2-1=7 个。
    计算公式:(SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))/2-1

    问:5.14 那这7个重定位项到底是怎样重定位的呢?还是给出具体结果更彻底。
    答:5.14 好,做事做到底,现在我们就开始吧。
    第一项:28 30 其内容为 0x3028  重定位类型为3, 地址为0x28
    其它重定位类型都是3, 地址不同而已。
    #define IMAGE_REL_BASED_HIGHLOW               3
    那么这个BASE_HIGHLOW 是什么意思呢?我们先看看0x28处的指令。
    10001026  FF0500300010                  inc  [L10003000]
    假如我们的dll 不是被加载到0x10000000,而是加载到0x30000000, 即向上提高了0x20000000
    我们只要把0x28地址处也加上0x20000000,就可以了。这样这条指令就变成了
    10001026  FF0500300030                  inc  [L30003000]
    看明白了吧! 窗户纸就是这样被捅破的


    好了,本帖就到这里结束吧!



    [ 本帖最后由 xbb[DFCG] 于 2008-6-2 09:20 编辑 ]

    Counter.rar

    561 Bytes, 下载次数: 8, 下载积分: 飘云币 -2 枚

    PYG19周年生日快乐!

    该用户从未签到

    发表于 2008-6-2 09:23:58 | 显示全部楼层
    沙发...顶.真精彩..
    PYG19周年生日快乐!

    该用户从未签到

    发表于 2008-6-2 11:44:02 | 显示全部楼层
    呵呵,这是一个经典的实例吧
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

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