您当前的位置: 首页 >  网络

哆啦A梦_i

暂无认证

  • 3浏览

    0关注

    629博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

计算机网络:逆向工程(精细版)

哆啦A梦_i 发布时间:2019-05-09 00:12:08 ,浏览量:3

(一)汇编、逆向工程基础篇

本系列文章将讲解逆向工程的各种知识,难度由浅入深。 

汇编是逆向工程的基础,这篇文章讲解并不深入但是覆盖了你刚开始学习汇编需要了解的所有基础知识!汇编语言是一切程序的起点和终点,毕竟所有的高级语言都是建立在汇编基础之上的。在许多高级语言中我们都需要相对明确的语法,但是在汇编中,我们会使用一些单词缩写和数字来表达程序。

I. 单元、位和字节

  1. ·BIT(位) - 电脑数据量的最小单元,可以是0或者1。

  2. 例:00000001 = 1;00000010 = 2;00000011 = 3     

  3. ·BYTE(字节) - 一个字节包含8个位,所以一个字节最大值是255(0-255)。为了方便阅读,我们通常使用16进制来表示。

  4. ·WORD(字) - 一个字由两个字节组成,共有16位。一个字的最大值是0FFFFh (或者是 65535d) (h代表16进制,d代表10进制)。

  5. ·DOUBLE WORD(双字DWORD) - 一个双字包含两个字,共有32位。最大值为0FFFFFFFF (或者是 4294967295d)。

  6. ·KILOBYTE(千字) - 千字节并不是1000个字节,而是1024 (32*32) 个字节。

  7. ·MEGABYTE - 兆字节同样也不是一兆个字节,而是1024*1024=1,048,576 个字节

II. 寄存器

寄存器是计算机储存数据的“特别地方”。你可以把寄存器看作一个小盒子,我们可以在里面放很多东西:比如名字、数字、一段话……

如今Win+Intel CPU组成的计算机通常有9个32位寄存器 (w/o 标志寄存器)。它们是:

  1. EAX: 累加器
  2. EBX: 基址寄存器

  3. ECX: 计数器

  4. EDX: 数据寄存器

  5. ESI: 源变址寄存器

  6. EDI: 目的变址寄存器

  7. EBP: 扩展基址指针寄存器

  8. ESP: 栈指针寄存器

  9. EIP: 指令指针寄存器

通常来说寄存器大小都是32位 (四个字节) 。它们可以储存值为从0-FFFFFFFF (无符号)的数据。起初大部分寄存器的名字都暗示了它们的功能,比如ECX=计数,但是现在你可以使用任意寄存器进行计数 (只有在一些自定义的部分,计数才必须用到ECX)。当我用到EAX、EBX、ECX、EDX、ESI和EDI这些寄存器时我才会详细解释其功能,所以我们先讲EBP、ESP、EIP。 

  1. EBP: EBP在栈中运用最广,刚开始没有什么需要特别注意的 ;) 

  2. ESP: ESP指向栈区域的栈顶位置。栈是一个存放即将会被用到的数据的地方,你可以去搜索一下push/pop 指令了解更多栈知识。 

  3. EIP: EIP指向下一个将会被执行的指令。

还有一件值得注意的事:有一些寄存器是16位甚至8位的,它们是不能直接寻址的。

包括:

通常一个寄存器可以这样看:

由图可知,EAX是这个32位寄存器的名字,EAX的低16位部分被称作AX,AX又分为高8位的AH和低8位的AL两个独立寄存器。 

注意:即使不怎么重要,你至少也要知道以下的寄存器

这些寄存器可以帮助我们区分大小: 

i. 单字节(8位)寄存器: 顾名思义,这些寄存器都是一个字节 (8位) :

  1. AL and AH

  2. BL and BH

  3. CL and CH

  4. DL and DH

ii. 单字(16位)寄存器: 这些寄存器大小为一个字 (=2 字节 = 16 位)。一个单字寄存器包含两个单字节寄存器。我们通常根据它们的功能来区分它们。 

1. 通用寄存器:

  1. AX (单字=16位) = AH + AL -> 其中‘+’号并不代表把它们代数相加。AH和AL寄存器是相互独立的,只不过都是AX寄存器的一部分,所以你改变AH或AL (或者都改变) ,AX寄存器也会被改变。 

  2. -> 'accumulator'(累加器):用于进行数学运算

  3. BX -> 'base'(基址寄存器):用来连接栈(之后会说明)

  4. CX -> 'counter'(计数器):

  5. DX -> 'data'(数据寄存器):大多数情况下用来存放数据

  6. DI -> 'destination index'(目的变址寄存器): 例如将一个字符串拷贝到DI

  7. SI -> 'source index'(源变址寄存器): 例如将一个字符串从SI拷贝

2. 索引寄存器(指针寄存器): 

  1. BP -> 'base pointer'(基址指针寄存器):表示栈区域的基地址

  2. SP -> 'stack pointer'(栈指针寄存器):表示栈区域的栈顶地址

3. 段寄存器:

  1. CS -> 'code segment'(代码段寄存器):用于存放应用程序代码所在段的段基址(之后会说明)

  2. DS -> 'data segment'(数据段寄存器):用于存放数据段的段基址(以后会说明)

  3. ES -> 'extra segment'(附加段寄存器):用于存放程序使用的附加数据段的基地址

  4. SS -> 'stack segment'(栈段寄存器):用于存放栈段的段基址(以后会说明)

4. 指令指针寄存器:

IP -> 'instruction pointer'(指令指针寄存器):指向下一个指令 ;)

iii. 双字(32位)寄存器:

2 字= 4 字节= 32 位, EAX、EBX、ECX、EDX、EDI…… 

如果16位寄存器前面加了‘E’,就代表它们是32位寄存器。例如,AX=16位,EAX=32位。 

III. 标志寄存器

标志寄存器代表某种状态。在32位CPU中有32个不同的标志寄存器,不过不用担心,我们只关心其中的3个:ZF、OF、CF。在逆向工程中,你了解了标志寄存器就能知道程序在这一步是否会跳转,标志寄存器就是一个标志,只能是0或者1,它们决定了是否要执行某个指令。

Z-Flag(零标志):

ZF是破解中用得最多的寄存器(通常情况下占了90%),它可以设成0或者1。若上一个运算结果为0,则其值为1,否则其值为0。(你可能会问为什么‘CMP’可以操作ZF寄存器,这是因为该指令在做比较操作(等于、不等于),那什么时候结果是0什么时候是1呢?待会再说) 

The O-Flag(溢出标志):

OF寄存器在逆向工程中大概占了4%,当上一步操作改变了某寄存器的最高有效位时,OF寄存器会被设置成1。例如:EAX的值为7FFFFFFFF,如果你此时再给EAX加1,OF寄存器就会被设置成1,因为此时EAX寄存器的最高有效位改变了(你可以使用电脑自带计算器将这个16进制转化成2进制看看)。还有当上一步操作产生溢出时(即算术运算超出了有符号数的表示范围),OF寄存器也会被设置成1。 

The C-Flag(进位标志):

进位寄存器的使用大概占了1%,如果产生了溢出,就会被设置成1。例,假如某寄存器值为FFFFFFFF,再加上1就会产生溢出,你可以用电脑自带的计算器尝试。 

IV. 段偏移 

内存中的一个段储存了指令(CS)、数据(DS)、堆栈(SS)或者其他段(ES)。每个段都有一个偏移量,在32位应用程序下,这些偏移量由 00000000 到 FFFFFFFF。段和偏移量的标准形式如下:

段:偏移量 = 把它们放在一起就是内存中一个具体的地址。

可以这样看:

一个段是一本书的某一页:偏移量是一页的某一行

V. 栈

栈是内存里可以存放稍后会用到的东西的地方。可以把它看作一个箱子里的一摞书,最后一本放进去的永远是最先出来的。或者把栈看作一个放纸的盒子,盒子是栈,而每一张纸就代表了一个内存地址。总之记住这个规则:最后放的纸最先被拿出来。’push’命令就是向栈中压入数据,‘pop’命令就是从栈中取出最后放入的数据并且把它存进具体的寄存器中。

VI. 指令 (字母表排序) 

请注意,所有的值通常是以16进制形式储存的。

大部分指令有两个操作符 (例如:add EAX, EBX),有些是一个操作符 (例如:not EAX),还有一些是三个操作符 (例如:IMUL EAX、EDX、64)。如果你使用 “DWORD PTR [XXX]”就表示使用了内存中偏移量为[XXX]的的数据。注意:字节在内存中储存方式是倒过来的(Win+Intel的电脑上大部分采用”小端法”, WORD PTR [XXX](双字节)和 BYTE PTR [XXX](单字节)也都遵循这一规定)。 

大部分有两个操作符的指令都是以下这些形式(以add指令举例):

add eax,ebx                          ;; 寄存器, 寄存器
add eax,123                          ;; 寄存器, 数值
add eax,dword ptr [404000]           ;; 寄存器, Dword  指针 [数值]
add eax,dword ptr [eax]              ;; 寄存器, Dword  指针 [寄存器值]
add eax,dword ptr [eax+00404000]     ;; 寄存器, Dword  指针 [寄存器值+数值]
add dword ptr [404000],eax           ;; Dword 指针[数值], 寄存器
add dword ptr [404000],123           ;; Dword 指针[数值], 数值
add dword ptr [eax],eax              ;; Dword 指针[寄存器值], 寄存器
add dword ptr [eax],123              ;; Dword 指针[寄存器值], 数值
add dword ptr [eax+404000],eax       ;; Dword 指针[寄存器值+数值], 寄存器
add dword ptr [eax+404000],123       ;; Dword 指针[寄存器值+数值], 数值

ADD (加)

语法: ADD 被加数, 加数

加法指令将一个数值加在一个寄存器上或者一个内存地址上。

add eax,123 = eax=eax+123;

加法指令对ZF、OF、CF都会有影响。

AND (逻辑与)

语法: AND 目标数, 原数    

AND运算对两个数进行逻辑与运算。

AND指令会清空OF,CF标记,设置ZF标记。

为了更好地理解AND,这里有两个二进制数:

1001010110
0101001101

如果对它们进行AND运算,结果是0001000100

即同真为真(1),否则为假(0),你可以用计算器验证。

CALL (调用)

语法:CALL something

CALL指令将当前的相对地址(IP)压入栈中,并且调用CALL 后的子程序

CALL 可以这样使用:

CALL 404000                ;; 最常见: CALL 地址
CALL EAX                   ;; CALL 寄存器 - 如果寄存器存的值为404000,那就等同于第一种情况
CALL DWORD PTR [EAX]       ;; CALL [EAX]偏移量所指向的地址
CALL DWORD PTR [EAX+5]     ;; CALL [EAX+5]偏移量所指向的地址

CDQ

Syntax: CDQ

CDQ指令第一次出现时通常不好理解。它通常出现在除法前面,作用是将EDX的所有位变成EAX最高位的值,

比如当EAX>=80000000h时,其二进制最高位为1,则EDX被32位全赋值为1,即FFFFFFFF

若EAXMark Zbikowski(设计了DOS的工程师)”

  •   USHORT e_cblp; // 文件最后页的字节数

  •   USHORT e_cp; // 文件页数

  •   USHORT e_crlc; // 重定义元素个数

  •   USHORT e_cparhdr; // 头部尺寸,以段落为单位

  •   USHORT e_minalloc; // 所需的最小附加段

  •   USHORT e_maxalloc; // 所需的最大附加段

  •   USHORT e_ss; // 初始的SS值(相对偏移量)

  •   USHORT e_sp; // 初始的SP值

  •   USHORT e_csum; // 校验和

  •   USHORT e_ip; // 初始的IP值

  •   USHORT e_cs; // 初始的CS值(相对偏移量)

  •   USHORT e_lfarlc; // 重分配表文件地址

  •   USHORT e_ovno; // 覆盖号

  •   USHORT e_res[4]; // 保留字

  •   USHORT e_oemid; // OEM标识符(相对e_oeminfo)

  •   USHORT e_oeminfo; // OEM信息

  •   USHORT e_res2[10]; // 保留字

  •   LONG e_lfanew; // 指示NT头的偏移(根据不同文件拥有可变值)

  • } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

  • 其中比较重要的有e_magic和e_lfanew,由图可知

    e_magic的值为4D5A,e_lfanew的值为000000C0(注意不是C0000000,详见我的上一篇文章)

    WORD占2个字节,LONG占4个字节,刚好是30个WORD和1个LONG,从00000000到0000003F 

    DOS存根:

    即使没有DOS存根,文件也能正常执行

    NT头(PE最重要的头):

    其定义如下:

     
    1. typedef struct _IMAGE_NT_HEADERS { 

    2.         DWORD Signature; 

    3.         IMAGE_FILE_HEADER FileHeader; 

    4.         IMAGE_OPTIONAL_HEADER32 OptionalHeader; 

    5. } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;  

    6.  

    7. Signature:类似于DOS头中的e_magic,其高16位是0,低16是0x4550,用字符表示是'PE‘(00004550)。

    8. IMAGE_FILE_HEADER:IMAGE_FILE_HEADER是PE文件头,定义如下:

    9.     typedef struct _IMAGE_FILE_HEADER { 

    10.         WORD    Machine; 

    11.         WORD    NumberOfSections; 

    12.         DWORD   TimeDateStamp; 

    13.         DWORD   PointerToSymbolTable; 

    14.         DWORD   NumberOfSymbols; 

    15.         WORD    SizeOfOptionalHeader; 

    16.         WORD    Characteristics; 

    17. } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

     

    其中有4个重要的成员(若设置不正确,将会导致文件无法正常运行)

    #1.Machine

    每个CPU拥有唯一的Machine码,兼容32位Intel X86芯片的Machine码为14C(如图)。以下是定义在winnt.h文件中的Machine码:

    1. #define IMAGE_FILE_MACHINE_UNKNOWN           0 

    2.     #define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386. 

    3.     #define IMAGE_FILE_MACHINE_R3000             0x0162  // MIPS little-endian, 0x160 big-endian 

    4.     #define IMAGE_FILE_MACHINE_R4000             0x0166  // MIPS little-endian 

    5.     #define IMAGE_FILE_MACHINE_R10000            0x0168  // MIPS little-endian 

    6.     #define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169  // MIPS little-endian WCE v2 

    7.     #define IMAGE_FILE_MACHINE_ALPHA             0x0184  // Alpha_AXP 

    8.     #define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian 

    9.     #define IMAGE_FILE_MACHINE_SH3DSP            0x01a3 

    10.     #define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian 

    11.     #define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian 

    12.     #define IMAGE_FILE_MACHINE_SH5               0x01a8  // SH5 

    13.     #define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian 

    14.     #define IMAGE_FILE_MACHINE_THUMB             0x01c2 

    15.     #define IMAGE_FILE_MACHINE_AM33              0x01d3 

    16.     #define IMAGE_FILE_MACHINE_POWERPC           0x01F0  // IBM PowerPC Little-Endian 

    17.     #define IMAGE_FILE_MACHINE_POWERPCFP         0x01f1 

    18.     #define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64 

    19.     #define IMAGE_FILE_MACHINE_MIPS16            0x0266  // MIPS 

    20.     #define IMAGE_FILE_MACHINE_ALPHA64           0x0284  // ALPHA64 

    21.     #define IMAGE_FILE_MACHINE_MIPSFPU           0x0366  // MIPS 

    22.     #define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466  // MIPS 

    23.     #define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64 

    24.     #define IMAGE_FILE_MACHINE_TRICORE           0x0520  // Infineon 

    25.     #define IMAGE_FILE_MACHINE_CEF               0x0CEF 

    26.     #define IMAGE_FILE_MACHINE_EBC               0x0EBC  // EFI Byte Code 

    27.     #define IMAGE_FILE_MACHINE_AMD64             0x8664  // AMD64 (K8) 

    28.     #define IMAGE_FILE_MACHINE_M32R              0x9041  // M32R little-endian

    29.     #define IMAGE_FILE_MACHINE_CEE               0xC0EE

    #2.NumberOfEsctions

    PE文件把代码,数据,资源等依据属性分类到各节中储存。

    NumberOfEsctions指文件中存在的节段(又称节区)数量,也就是节表中的项数。该值一定要大于0,且当定义的节段数与实际不符时,将发生运行错误。

    #3.SizeOfOptionalHeader

    IMAGE_NT_HEADERS结构最后一个成员IMAGE_OPTIONAL_HEADER32。

    SizeOfOptionalHeader用来指出IMAGE_OPTIONAL_HEADER32结构体的长度。PE装载器需要查看SizeOfOptionalHeader的值,从而识别IMAGE_OPTIONAL_HEADER32结构体的大小。

    PE32+格式文件中使用的是IMAGE_OPTIONAL_HEADER64结构体,这两个结构体尺寸是不相同的,所以需要在SizeOfOptionalHeader中指明大小。

    #4.Characteristics

    该段用于标识文件的属性,文件是否是可运行的状态,是否为DLL文件等信息。

     
    1. #define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file. 

    2.     #define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable  (i.e. no unresolved externel references). 

    3.     #define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file. 

    4.     #define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file. 

    5.     #define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set 

    6.     #define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses 

    7.     #define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed. 

    8.     #define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine. 

    9.     #define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from file in .DBG file 

    10.     #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, copy and run from the swap file. 

    11.     #define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net, copy and run from the swap file. 

    12.     #define IMAGE_FILE_SYSTEM                    0x1000  // System File. 

    13.     #define IMAGE_FILE_DLL                       0x2000  // File is a DLL. 

    14.     #define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine 

    15.     #define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.

    为方便理解,上述程序的NT头内容如下:

    (成员功能概述)

    1. NumberOfSections:该PE文件中有多少个节段,也就是节表中的项数。

    2. TimeDateStamp:PE文件的创建时间,一般有连接器填写。

    3. PointerToSymbolTable:COFF文件符号表在文件中的偏移。

    4. NumberOfSymbols:符号表的数量。

    5. SizeOfOptionalHeader:紧随其后的可选头的大小。

    6. Characteristics:可执行文件的属性。

    IMAGE_OPTIONAL_HEADER32:

    其定义如下: 

    1. typedef struct _IMAGE_OPTIONAL_HEADER { 

    2.         WORD    Magic; 

    3.         BYTE    MajorLinkerVersion; 

    4.         BYTE    MinorLinkerVersion; 

    5.         DWORD   SizeOfCode; 

    6.         DWORD   SizeOfInitializedData; 

    7.         DWORD   SizeOfUninitializedData; 

    8.         DWORD   AddressOfEntryPoint; 

    9.         DWORD   BaseOfCode; 

    10.         DWORD   BaseOfData; 

    11.         DWORD   ImageBase; 

    12.         DWORD   SectionAlignment; 

    13.         DWORD   FileAlignment; 

    14.         WORD    MajorOperatingSystemVersion; 

    15.         WORD    MinorOperatingSystemVersion; 

    16.         WORD    MajorImageVersion; 

    17.         WORD    MinorImageVersion; 

    18.         WORD    MajorSubsystemVersion; 

    19.         WORD    MinorSubsystemVersion; 

    20.         DWORD   Win32VersionValue; 

    21.         DWORD   SizeOfImage; 

    22.         DWORD   SizeOfHeaders; 

    23.         DWORD   CheckSum; 

    24.         WORD    Subsystem; 

    25.         WORD    DllCharacteristics; 

    26.         DWORD   SizeOfStackReserve; 

    27.         DWORD   SizeOfStackCommit; 

    28.         DWORD   SizeOfHeapReserve; 

    29.         DWORD   SizeOfHeapCommit; 

    30.         DWORD   LoaderFlags; 

    31.         DWORD   NumberOfRvaAndSizes; 

    32.         IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; 

    33.     } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

    我们需要关注下列成员,这些事运行程序必需的,设置错误将导致程序无法正常运行。

    #1.Magic

    为IMAGE_OPTIONAL_HEADER32时,magic码为10B,为IMAGE_OPTIONAL_HEADER64时,magic码为20B

    #2.AddressOfEntryPoint

    AddressOfEntryPoint持有EP的RVA值。该值指出程序最先执行的代码起始地址,相当重要。

    #3.ImageBase

    一般来说,使用开发工具(VB/VC++/Delphi)创建好EXE文件后,其ImageBase值为00400000,DLL文件的ImageBase值为10000000(当然也可以指定其他值)。

    执行PE文件时,PE装载器先创建进程,再将文件载入内存,然后把EIP寄存器的值设置为ImageBase+AddressOfEntryPoint

    #4.SectionAlignment,FileAlignment

    PE文件的Body部分被划分成若干节段,这些节段储存着不同类别的数据。FileAlignment指定了节段在磁盘文件中的最小单位,而SectionAlignment则指定了节区在内存中的最小单位(SectionAlignment必须大于或者等于FileAlignment)

    #5.SizeOfImage

    当PE文件加载到内存时,SizeOfImage指定了PE Image在虚拟内存中所占用的空间大小,一般文件大小与加载到内存中的大小是不同的(节段头中定义了各节装载的位置与占有内存的大小,后面会讲到)

    #6.SizeOfHeader

    SizeOfHeader用来指出整个PE头大小。该值必须是FileAlignment的整数倍。第一节段所在位置与SizeOfHeader距文件开始偏移的量相同。

    #7.Subsystem

    Subsystem值用来区分系统驱动文件(*.sys)与普通可执行文件(*.exe,*.dll)。

    Subsystem成员可拥有值如下:

    1. #define IMAGE_SUBSYSTEM_UNKNOWN              0   // Unknown subsystem.  

    2. #define IMAGE_SUBSYSTEM_NATIVE               1   // Image doesn't require a subsystem.   系统驱动

    3. #define IMAGE_SUBSYSTEM_WINDOWS_GUI          2   // Image runs in the Windows GUI subsystem.  窗口应用程序

    4. #define IMAGE_SUBSYSTEM_WINDOWS_CUI          3   // Image runs in the Windows character subsystem.  控制台应用程序

    5. #define IMAGE_SUBSYSTEM_OS2_CUI              5   // image runs in the OS/2 character subsystem.  

    6. #define IMAGE_SUBSYSTEM_POSIX_CUI            7   // image runs in the Posix character subsystem.  

    7. #define IMAGE_SUBSYSTEM_NATIVE_WINDOWS       8   // image is a native Win9x driver.  

    8. #define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI       9   // Image runs in the Windows CE subsystem.  

    9. #define IMAGE_SUBSYSTEM_EFI_APPLICATION      10  //  

    10. #define IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER  11   //  

    11. #define IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER   12  //  

    12. #define IMAGE_SUBSYSTEM_EFI_ROM              13  

    13. #define IMAGE_SUBSYSTEM_XBOX                 14  

    14. #define IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16

    #8.DataDirectory

    数据目录,定义如下:

     
    1. ·  typedef struct _IMAGE_DATA_DIRECTORY {  

    2. ·      DWORD   VirtualAddress;  

    3. ·      DWORD   Size;  

    4. ·  } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

    可以看出,有地址(VirtualAddress)有大小(Size),数组定义的一定是一个区域,数组每项都有被定义的值,不同项对应不同数据结构,比如导入表,导出表等,定义如下:

    1. #define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory 

    2. #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory 

    3. #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory 

    4. #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory 

    5. #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory 

    6. #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table 

    7. #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory 

    8. //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage) 

    9. #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data 

    10. #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP 

    11. #define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory 

    12. #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory 

    13. #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers 

    14. #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table 

    15. #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors 

    16. #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

    各位重点关注标红的IMPORT和EXPORT,它们是PE头中的非常重要的部分,其它部分不怎么重要,大致了解下即可。 

    #9.NumberOfRvaAndSizes

    NumberOfRvaAndSizes用来指定DataDirectory的数组个数,虽然结构体定义中明确指出了数组个数为16,但也有可能不是16,PE装载器需要通过这个值来识别。

    各成员代表的值和偏移量就不一一写出了,累死咯。。。 

    (成员功能概述)

     
    1. Magic:表示可选头的类型。

    2. MajorLinkerVersion和MinorLinkerVersion:链接器的版本号。

    3. SizeOfCode:代码段的长度,如果有多个代码段,则是代码段长度的总和。

    4. SizeOfInitializedData:初始化的数据长度。

    5. SizeOfUninitializedData:未初始化的数据长度。

    6. AddressOfEntryPoint:程序入口的RVA,对于exe这个地址可以理解为WinMain的RVA。对于DLL,这个地址可以理解为DllMain的RVA,如果是驱动程序,可以理解为DriverEntry的RVA。当然,实际上入口点并非是WinMain,DllMain和DriverEntry,在这些函数之前还有一系列初始化要完成,当然,这些不是本文的重点。

    7. BaseOfCode:代码段起始地址的RVA。

    8. BaseOfData:数据段起始地址的RVA。

    9. ImageBase:映象(加载到内存中的PE文件)的基地址,这个基地址是建议,对于DLL来说,如果无法加载到这个地址,系统会自动为其选择地址。

    10. SectionAlignment:节对齐,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0。

    11. FileAlignment:节在文件中按此值对齐,SectionAlignment必须大于或等于FileAlignment。

    12. MajorOperatingSystemVersion、MinorOperatingSystemVersion:所需操作系统的版本号,随着操作系统版本越来越多,这个好像不是那么重要了。

    13. MajorImageVersion、MinorImageVersion:映象的版本号,这个是开发者自己指定的,由连接器填写。

    14. MajorSubsystemVersion、MinorSubsystemVersion:所需子系统版本号。

    15. Win32VersionValue:保留,必须为0。

    16. SizeOfImage:映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。

    17. SizeOfHeaders:所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。

    18. CheckSum:映象文件的校验和。

    19. Subsystem:运行该PE文件所需的子系统。

    DllCharacteristics:DLL的文件属性,只对DLL文件有效,可以是下面定义中某些的组合:

     

     
    1. SizeOfStackReserve:运行时为每个线程栈保留内存的大小。

    2. SizeOfStackCommit:运行时每个线程栈初始占用内存大小。

    3. SizeOfHeapReserve:运行时为进程堆保留内存大小。

    4. SizeOfHeapCommit:运行时进程堆初始占用内存大小。

    5. LoaderFlags:保留,必须为0。

    6. NumberOfRvaAndSizes:数据目录的项数,即下面这个数组的项数。

    7. DataDirectory:数据目录,这是一个数组。

    节段(区)头:

    PE文件有不同的节段:code(代码),data(数据),resource(资源),这样设计避免了很多安全问题,比如向data写数据,由于某原因导致溢出,其下的code就会被覆盖,程序就会崩溃。

    code/data/resource都有不同的权限,如下:

    节段头是由IMAGE_SECTION_HEADER结构体组成的数组,每个结构体对应一个节段。

     
    1. typedef struct _IMAGE_SECTION_HEADER {

    2.   BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];

    3.   union {

    4.     DWORD PhysicalAddress;

    5.     DWORD VirtualSize;

    6.   } Misc;

    7.   DWORD VirtualAddress;

    8.   DWORD SizeOfRawData;

    9.   DWORD PointerToRawData;

    10.   DWORD PointerToRelocations;

    11.   DWORD PointerToLinenumbers;

    12.   WORD  NumberOfRelocations;

    13.   WORD  NumberOfLinenumbers;

    14.   DWORD Characteristics;

    15. } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

    下表列出了需要了解的重要成员:

    VirtualAddress与PointerToRawData不带有任何值,分别由(定义在IMAGE_OPTIONAL_HEADER32中的)SectionAlignment和FileAlignment确定。

    上述程序有4个节段。

    如何运用我们上面学习到的PE知识?

    了解了PE知识,继续我们的破解工作!!

    我们在数据窗口(dump)有点选择跳转,跳转到起始位置(400000):

    点击OK

    我们成功跳转到了起始位置。

    点击右上方LEMTW的M查看内存情况。

    双击该处进入,看到DOS HEADER已经载入了,直接向下翻,查找DOS头的e_lfanew成员:

    查看到偏移量是000000C0,记住我们载入内存时,基准位置(ImageBase)是400000,相对虚拟地址(RVA)是:

    RVA=ImageBase+VA

    所以此时PE头位置是004000C0,我们向下翻到该处。

    开始找最重要的的AddressOfEntryPoint

    找到AddressOfEntryPoint=0×1000,我么需要让它跳转到401024:

    那我们修改其值,双击004000E9(00 10 00 00 –>00001000)的值进行修改,修改为24 10 00 00 (00001024):

    然后保存到可执行文件:

    保存完成后,用OD载入刚保存的RegisterMe1.0,发现入口已经变成了我们修改的00401024,成果跳过了第一个烦人的消息框!

    我们再向下执行:

    执行到call Register.00401052时弹出了“我们需要注册的信息”

    我们再向下执行:

    再第二个MessageBox处又弹出了消息框,我们这次采用NOP(No operation)填充:

    填充完成后,我们保存为可执行文件RegisterMe2.0。双击执行,再也没有可恶地消息框咯!

    (三):实例破解

    本系列文章将讲解逆向工程的各种知识,难度由浅入深。 

    补课:《逆向工程(一):汇编、逆向工程基础篇》、《逆向工程(二):从一个简单的实例来了解PE文件》

    这次我们将破解一款真正的商业程序。

    为了避免一些不必要的麻烦,选择的这款商业程序在2001年就停止维护了,所以我们可以放心大胆的破解学习!

    (不要在这里灰心,即便是2001年的软件,仍有我们值得学习的地方!!)

    这个软件也许我们在有些地方看过,很多教程都喜欢拿这个软件做靶子,但是我们还是要努力玩出一点花样,对不对,简简单单的破解当然学到的不会太多。

    文中软件及原视频下载地址 密码: x3tv

    无壳的实例

    目标:破解软件功能限制

    工具:OD

    安装好这款软件打开如图:

    这款软件有诸多功能限制,如:

    当我们选择Add Group,提示没有注册只能创建3个Group:

    选择Add,提示没有注册只能添加4个联系人:

    我们打开About PixtopianBook,弹出未注册版本信息。

    我们将其载入OD(建议下载一个原版OD#www.ollydbg.de#,我们尝试手动配置忽略异常)如图:

    点击运行后,并没有出现程序界面,而是抛出了异常:

    我们手动开启忽略异常:

    打开后选择 exception:

    勾选如上,再点击Add lase exception。

    配置完成后点击OK。

    配置完成后重新载入程序,点击运行,成功打开了程序。

    正式开始!

    1、Group

    将软件载入OD,点击Add Group。

    我们在其弹出消息框后暂停OD。

    此时状态栏显示:暂停,我们此时需要返回到程序领空,快捷键Alt+F9。

    然后点击消息框 确定 此时状态栏显示:返回到用户

    我们成功返回到程序领空,向上翻。

    看到了一个MessageBox,这就是我们刚才确定的那个消息框。

    向下我们将

    这个命令将带我们返回到调用CALL处。所以我们继续向下执行。

    进行retn:

    我们跳出了call,返回到了调用处、

    我们看到:

    1.这个Call就是我们刚才进入的Call,加入我们能跳过这个call,就能避免跳出消息框。

    2.查看这个ascii码,正是消息框的标题和内容,在这里被压入栈,等待调用。

    3.这里有一个cmp,比较eax与3大小(即Group是否等于3),如果小于就跳转到(JL)00408B34,如果已经有3个Group了,就执行call弹出未注册消息框。

    你一定已经知道我们将要干什么了!

    将JL(小于则跳转)改为jmp(强制跳转)。

    这样我们就成功跳过了GROUP验证。

    我们将其保存:

    测试一下:

    哈哈,已经可以自由添加Group了!!是不是很有成就感啊!学习逆向成就感很重要哦。

    2、联系人

    默认未注册用户只能添加四个联系人,我们采取同样方法:

    1、弹出消息框后暂停

    2、Alt+F9执行到用户代码

    3、点击确定

    跳转到这个位置,我们向上翻,看到同样的MessageBox,如下:

    通过retn跳出call,回到调用处,向上翻:

    1、这个call调用了MessageBox,弹出了消息框;

    2、这句话同我们看到的一样,确定程序在这里将其压入栈;

    3、比较eax与4的大小(联系人是否已经有4个),如果小于等于(JL)4个,那就跳过消息框,否则弹出未注册消息框。

    我们同样修改jl为jmp,强制其跳转:

    保存后测试一下:

    我们又成功搞定了一个功能!这个阉割版已经成了注册版,不过还有一些美中不足的地方。比如:

    那我们就开始让它从里到外都是注册版!

    3、更改版本信息

    标题,版本文字在代码区是找不到的,我们直接在内存中找

    输入查找关键字UNREGISTERED:

    如果ASCII没有搜索到就用UNICODE

    (这里ASCII也可以搜索到,不过不是版本信息,UNICODE下搜索到版本信息,点击确定):

    记录下004D4830,在dump面板跳转:

    跳转之后成功找到需要修改的内容:

    我们选择要修改的内容,右键编辑:

    编辑为自己想写的字符,如下:

    4、DIY标题

    同样是在内存中查找,在弹出的搜索框里输入关键字:(UNREGISTERED VER

    (一般情况下是不知道要搜索的文字是ascii还是unicode,我们会都尝试搜索一次。

    记下004E4BE6,在dump窗口跟随:

    找到需要修改的字符然后编辑:

    确定后如下图所示:

    5、更改提示注册文字

    同理,我们在内存中搜索:

    首先尝试UNICODE,没有搜索到结果。

    再尝试ASCII搜索:

    成功搜到了字符串:

    记录下位置48F974,并跳转到该处:

    用同样的方法编辑ASCII码:

    编辑完成后如下:

    在所有工作做完之后,不要忘记保存:

    保存后我们查看效果:

    6、结尾

    我们仍发现了一些美中不足的地方。

    在进入程序时,首先是欢迎信息:

    几秒后变为:

    我们想让它更加简洁,一直显示welcome信息,该怎么操作呢?

    那我们开始进行最后一步!!

    通过上面的操作我们已经知道了注册文字在48F974处,在dump(数据窗口)窗口右键跳转到48F974

    我们需要知道程序哪一段调用了这段字符串。

    我们右键选择查找参考(Ctrl+R)(个人觉得做汉化翻译得有点问题,这个命令的英文是Find references,意思应该是查找引用,参考这个词不太合适):

    找到一处引用,我们双击进入:

    我们来到如图所示的代码位置:

    发现上面有一处cmp和jnz(cmp与jnz组合代表:如果不相等,则跳转),如果ebp等于907,则将这串字符压入栈(可以猜测,907代表未注册),我们根据跳转向上翻:

    向上翻到这里:

    又有一处cmp和jnz,意思是如果ebp不等于906,就要跳转回0040C22F处。

    (我们可以猜测,906代表注册过)

    这个程序我们已经完全弄清楚了,我们直接在0040C235的jnz处进行强制跳转,让其不会将这串字符压入栈:

    修改后,我们最后一次保存:

    打开:

    整款软件已经被我们完全破解了!!

    虽然这是一款2001年的软件,但这是我们破解的第一款真正的软件!所以今晚好好庆祝一下吧,我们在下一篇教程里再见!!

    关注
    打赏
    1556978864
    查看更多评论
    立即登录/注册

    微信扫码登录

    0.0522s