《加密与解密》 笔记

 

     

    汇编基础:

    二进制左移一位 相当于原来的2=X2,十六进制左移一位相当于原来的16=x16

    十进制左移一位相当于扩大10

    反过来二进制右移一位 相当于原来的1/2十六进制右移一位相当于原来的1/16

    十进制左移一位相当于 原来的1/10

    对内存的理解 内存中的每个存储单元就相当于 ,图书馆里书架的每个格子,比如说有1010列,每一行用

    0-9标识,每一列也用0-9标识。RAM相当于一个矩阵 行列组合 CPU要读取数据的时候,CPU会向地址总线传送行位和列位,在X86处理器中到达行和列的地址译码器。这就确定数据位置了然后从数据总线将数据传给CPU

    二进制正数转换负数方法

    (1)正负表示方法

    用字节的最高位表示:"1"表示"","0"表示""

     

    (2)计算机中数字是以哪个码储存的?

    补码

     

    (3) 负数 的二进制补码转换成十进制的方法

    1、把补码“取反”(把二进制数的各位“1”换“0”,“0”换“1”。比如“101010”取反后为“010101”)

    2、把取反后的二进制数“加1

    3、最后用常规的方法把“加1”后的二进制数转换为十进制数

    第一章 基础知识

     

    ASCII:美国信息交换标准码(American Standard Code for information Inerchange)

    8 128个代码26个小写字母26个大写字母10个数字32个字符号33个控制代码和一个空格

    intelCPU在内存中低位存入低地址 高位存入高地址

    Unicode:是ASCII字符编码的一个扩展。称为"宽字符集"

     

    API函数:它是区分字符集的 A代表ANSI是以单字节表示 W表示WideCharsUnicode是以双字节表示.其实USER32.DLL没有32Messagebox函数入口点的, MessageboxA(ANSI) MessageboxW(Unicode) 编译器自己选择用哪个

    MessageBoxEx 其实 windows9x/2000/xp下直接调用这个函数


    int MessageBoxEx(      

     

        HWND hWnd,


        LPCTSTR lpText,


        LPCTSTR lpCaption,


        UINT uType,


    WORD wLanguageId //代表语言标识


    );

    windows9x中大多使用ASCI来内部操作的 ,但是编程时可以调用MessageBoxWUnicode

    windows98中调用MessageBoxW, 其实内部最终还是会依次调用以下函数转换位ASCI.

    WideCharTOMultiByte()//取得字符串长度;GlobalAlloc//按字节长度分配内存,在调用WideCharTOMultiByte() 将字符串转换为ASCI字符串 紧接着还是调用ASCI版的MessageboxA来显示窗口, 然后在 GlobalFree释放内存.

     

    2000/xp中最终会使用unicode字符串 ,将调用的ASCI函数 转换为 Unicode ,例如调用MessageboxA 接下来会直接转换为Unicode 然后在调用MessageBoxW, 如果直接调用Unicode函数会减少系统开销 省去MessageBoxA的步骤

     

    WIN32:用于32位版本的windowsAPI称作WIN32

     

     

     

    Windows注册表:

    主键类型

  1. HKEY_CLASSES_ROOT 简称HKCR 包含了文件扩展名和COM组件类的注册信息。
  2. HKEY_CURRENT_USER 简称HKCU 包含了登陆用户相关的软件配置和参数.
  3. HKEY_LOCAL_MACHINE 简称HKLM 用来控制系统和软件的设置
  4. HKEY_USERS 简称 HKU 包含关于动态加载的用户配置文件和默认的配置文件的信息,同时还包含了HKEY_CURRENT_USER中的信息
  5. HKEY_CURRENT_CONFIG 包含了启动时本地计算机系统使用的硬件配置文件和相关信息
  6. 注册表相关函数: 包含于ADVAPI32.DLL

    打开子键

    LONG RegOpenKeyEx(HKEY hkey, //要打开的主键句柄或标准项名

    LPRCTSTR lpSubKEy,//要打开的子键名地址

    DWORD ulOptions,//保留 ,必须为0

    REGSAM samDesired,//存取掩码

    PHKEY phkResult //存放打开子键句柄的地址

    );

    RegQueryValueEx 获取一个项的设置值

     

    Win32API操作windows注册表的基本步骤

  7. RegOpenKey()RegOpenKeyEx()打开想要操作的主键获得一个句柄
  8. 将句柄传递给RegQueryValueEx(),RegSetValueEX()等函数来读写相应的键值
  9. 操作完毕后用RegCloseKey()关闭先前获得的句柄
  10. 保护模式简介:

    80x86(80386及其以后的各带CPU)可以在实模式保护模式虚拟86模式下运转,实模式实古老的MS-DOS运行环境 windows是保护模式下运行的

    虚拟内存(virtual memory)

    在保护模式下CPU的寻址方式内存是线性的,因为这时段寄存器的意义不同 段寄存器存放段选择子,只是全局描述表(Global Descriptor Table,简称GDT)或本地描述表(Local Descriptor Table,简称LDT)的一个指针。不同段寄存器有不同的属性(,,执行,特权级)

     

     

     

    Win32每个进程都有属于自己的虚拟空间,32位进程地址空间是4GB,因为32位指针拥有0x00000000`0xFFFFFFFF之间任何一个地址。虚拟内存不是真正的内存,它通过映射(Map)的方法使可用的虚拟内存达到4GB2GB用于程序,2GB用于系统。

    虚拟内存实现的方法和过程

  11. 程序被启动时,系统创建一个新进程,并分配给它2GB的虚拟地址(只是地址,不是内存);
  12. 虚拟内存管理器(virtual memory Manager) 将程序代码映射到那个应用程序的虚拟地址中的某个位置,并把所有代码读取到物理地址中
  13. 其他项目 如堆栈,数据的空间是从物理内存中分配的,并映射到虚拟地址空间中
  14. 保护模式的权限级别(privilege level)

    最少分4 ring0,ring1,ring2,ring3. ring0为最好权限 ring3最低

     

     

     

     

     

     

    第二章代码分析技术

    认识PE格式 区段

    .text编译或者汇编结束产生的一种区段,内容是指令代码;

    .rdata 运行期只读数据;

    .data 初始化的数据段;

    .idata 包含其外来DLL的函数及数据信息,即输入表;

    .rsc 包含模块的全部资源,图标菜单。。等

    PE在磁盘上的数据结构和在内存中是一致的,主要是将PE文件某地方映射到地址空间中

    PE名词

    入口点(Entry Point) 程序执行入口

    文件偏移地址(File offset):文件在磁盘上时各数据地址叫做文件偏移地址或者物理地址(RAW offset)

    虚拟地址:所有程序访问存储器所使用的逻辑地址

    基地址(Imagebase):映射到内存中的制定地址的初始值,不同编译器出来的程序基地址可能不同 大多可能固定不变

    相对虚拟地址:RVA 是内存中相对于PE文件装入地址(基地址)的偏移量

    相对虚拟地址(RVA)=虚拟内存地址(virual address)-基地址(Imagebase)

     

     

    x86系统中,每个内存分页的大小是4KB0x1000字节,每个区段按0x1000之倍数的内存编译位置开始。如上图所示磁盘文件区段是0x400字节的倍数,在每个区段中多余的部分用0填充

    公式为

    File offset=RVA-OK

    File offset = VA-Imagebase-OK

    PE数据结构详解

    PE起始为DOS头部 包括DOS MZ,DOS STUB(一个完整的DOS程序用于DOS系统)

    typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header

    WORD e_magic; // Magic number

    WORD e_cblp; // Bytes on last page of file

    WORD e_cp; // Pages in file

    WORD e_crlc; // Relocations

    WORD e_cparhdr; // Size of header in paragraphs

    WORD e_minalloc; // Minimum extra paragraphs needed

    WORD e_maxalloc; // Maximum extra paragraphs needed

    WORD e_ss; // Initial (relative) SS value

    WORD e_sp; // Initial SP value

    WORD e_csum; // Checksum

    WORD e_ip; // Initial IP value

    WORD e_cs; // Initial (relative) CS value

    WORD e_lfarlc; // File address of relocation table

    WORD e_ovno; // Overlay number

    WORD e_res[4]; // Reserved words

    WORD e_oemid; // OEM identifier (for e_oeminfo)

    WORD e_oeminfo; // OEM information; e_oemid specific

    WORD e_res2[10]; // Reserved words

    LONG e_lfanew; // File address of new exe header

    } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

    PE文件头

    typedef struct _IMAGE_NT_HEADERS {

    DWORD Signature; // PE文件标识 PE/0/0

    IMAGE_FILE_HEADER FileHeader;

    IMAGE_OPTIONAL_HEADER32 OptionalHeader;

    } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

     

    typedef struct _IMAGE_FILE_HEADER {

    WORD Machine;

    WORD NumberOfSections;

    DWORD TimeDateStamp;

    DWORD PointerToSymbolTable;

    DWORD NumberOfSymbols;

    WORD SizeOfOptionalHeader;

    WORD Characteristics;

    } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

     

    typedef struct _IMAGE_OPTIONAL_HEADER {

    //

    // Standard fields.

    //

     

    WORD Magic;

    BYTE MajorLinkerVersion;

    BYTE MinorLinkerVersion;

    DWORD SizeOfCode;

    DWORD SizeOfInitializedData;

    DWORD SizeOfUninitializedData;

    DWORD AddressOfEntryPoint;

    DWORD BaseOfCode;

    DWORD BaseOfData;

     

    //

    // NT additional fields.

    //

     

    DWORD ImageBase; //装入内存时的VA

    DWORD SectionAlignment;//装入内存时的 对齐值

    DWORD FileAlignment;//文件对齐值

    WORD MajorOperatingSystemVersion;

    WORD MinorOperatingSystemVersion;

    WORD MajorImageVersion;

    WORD MinorImageVersion;

    WORD MajorSubsystemVersion;

    WORD MinorSubsystemVersion;

    DWORD Win32VersionValue;

    DWORD SizeOfImage;//装入内存后映像大小,从基地址到最后一块

    DWORD SizeOfHeaders;//DOSPE头 区块表总尺寸

    DWORD CheckSum;//校验和 IMAGEENHLP.DLL中的CheckSumMappedFile可以计算,驱动系统DLL才需要这个值 默认为0

    WORD Subsystem;

    WORD DllCharacteristics;

    DWORD SizeOfStackReserve;

    DWORD SizeOfStackCommit;

    DWORD SizeOfHeapReserve;

    DWORD SizeOfHeapCommit;

    DWORD LoaderFlags;

    DWORD NumberOfRvaAndSizes;

    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//IMAGE_DATA_DIRECTORY组成的结构数组共16

    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

    代码指令机器码的计算:

    短转移(short jump):无条件转移和条件转移机器码都为2字节,转移范围为-128-+127

    长转移(long jump):无跳转转移机器码为5个字节,条件转移为6个字节.无条件转移要用1个字节表示转移条件,剩下4字节表示转移偏移,条件转移要用2个字节来当转移条件,剩下4表示转移偏移

    子程序调用CALL:两种CALL一种是普通类似与长转移另一种是有寄存器参数堆栈等信息的

     

    指令修改技巧:

     

     

     

     

    逆向分析技术:

    高级语言中子程序依赖堆栈来传递参数,也就是说调用者把参数压进堆栈,然后子程序在取出使用

    调用约定是指调用例程时参数的传递顺序和约定平衡堆栈的程序

    实例:

    比如 我们有这样一个C函数

    #include<stdio.h>

    long test(int a,int b)

    {

        a = a + 1;

        b = b + 100;

        return a + b;

    }

    void main()

    {  

      printf("%d",test(1000,2000));

    }

     

    写成32位汇编就是这样

    ;//////////////////////////////////////////////////////////////////////////////////////////////////////

    .386

    .model flat,stdcall           ;这里我们用stdcall 就是函数参数 压栈的时候从最后一个开始压,和被调用函数负责清栈

    option casemap:none            ;区分大小写

     

    includelib msvcrt.lib          ;这里是引入类库 相当于 #include<stdio.h>       

    printf  PROTO C:DWORD,:VARARG  ;这个就是声明一下我们要用的函数头,到时候 汇编程序会自动到msvcrt.lib里面找的了 

                                    ;:VARARG 表后面的参数不确定 因为C就是这样的printf(const char *, ...);

                                   ;这样的函数要注意 不是被调用函数负责清栈 因为它本身不知道有多少个参数

                                   ;而是有调用者负责清栈  下面会详细说明

    .data

    szTextFmt  BYTE '%d',0        ;这个是用来类型转换的,跟C的一样,字符用字节类型

    a          dword 1000         ;假设

    b          dword 2000         ;处理数值都用双字 没有int long 的区别

     

    ;/////////////////////////////////////////////////////////////////////////////////////////

    .code

     

    _test proc ;A:DWORD,B:DWORD 

          push ebp

          mov  ebp,esp

          mov  eax,dword ptr ss:[ebp+8]

          add  eax,1

          mov  edx,dword ptr ss:[ebp+0Ch]

          add  edx,100

          add  eax,edx

          pop  ebp      

          retn 8

    _test endp

     

    _main proc 

          push dword ptr ds:b       ;反汇编我们看到的b就不是b了而是一个[*****]数字 dword ptr 就是我们在ds(数据段)[*****]

                                    ;开始的一个双字长数值取出来

          push dword ptr ds:a       ;跟她对应的还有 byte ptr ****就是取一个字节出来 比如这样 mov  al,byte ptr ds:szTextFmt 

                                    ;就把 取出来 而不包括 d

          call _test                  

          push eax                  ;假设push eax的地址是×××××

          push offset szTextFmt

          call printf

          add  esp,8

          ret             

    _main endp

    end  _main

     

    ;////////////////////////////////////////////////////////////// 下面介绍堆栈的变化

    首先要明白的是 操作堆栈段 ss 只能用 espebp寄存器 其他的寄存器eax ebx edx等都不能够用  esp永远指向堆栈栈顶 ebp用来 在堆栈段

     

    里面寻址

    push 指令是压栈 ESP=ESP-4

    pop  指令是出栈 ESP=ESP+4

    我们假设main函数一开始堆栈定是 ESP=400

    push dword ptr ds:b                 ;ESP-4=396 ->里面的值就是 2000 就是b的数值

    push dword ptr ds:a                 ;ESP-4=392 ->里面的值就是 1000 就是a的数值

    call test                           ;ESP-4=388>里面的数值是什么?这个太重要了 就是我们用来找游戏函数的原理所在。

                                                     里面的数值就是call test 指令下一条指令的地址->push eax的地址×××××

     

    到了test函数里面

     

    push ebp                           ;ESP-4=384->里面保存了当前ebp的值 而不是把ebp清零

    mov  ebp,esp                       ;这里ESP384就没变化了,但是 ebp=esp=384,为什么要这样做呢 因为我们要用ebp到堆栈里面找参数

    mov  eax,dword ptr ss:[ebp+8]      ;反汇编是这样的 想想为什么a就是[ebp+8]

                                       ;我们往上看看堆栈里地址392处就保存着a的值 这里ebp=384 加上8正好就是392

                                       ;这样就把传递过来的1000拿了出来eax=1000

    add  eax,1                         ;相当于 a+1 eax=1001

    mov  edx,dword ptr ss:[ebp+0Ch]    ; 0Ch=12 一样道理这里指向堆栈的地址是384+12=396 就是2000 edx=2000

    add  edx,100                       ;相当于 b+100 edx=2100

    add  eax,edx                       ;eax=eax+edx100121003101 这里eax已经保存了最终的结果了 

                                       ;因为win32汇编一般用eax返回结果 所以如果最终结果不是在eax里面的话 还要把它放到eax

                                       ;比如假设我的结果保存在变量nRet里面 最后还是要这样 mov eax,dword ptr nRet

    pop  ebp                           ;ESP=384+4=388 而保存在栈顶384的值 保存到 ebp 即恢复ebp原来的值                       

                                       ;因为一开始我们就把ebp的值压栈了,mov ebp,esp已经改变了ebp的值,这里恢复就是保证了堆栈平衡

    retn  8                            ;ESP+8->396 这里retn是由系统调用的 我们不用管 系统会自动把EIP指针指向 原来的call的下一条指令

                                       ;由于是系统自动恢复了call那里的压栈所以 真正返回到的时候ESP+4就是恢复了call压栈的堆栈

                                       ;到了这个时候 ESP=400 就是函数调用开始的堆栈,就是说函数调用前跟函数调用后的堆栈是一样的

                                       ;这就是堆栈平衡 

    由于我们用stdcall上面retn 8就是被调用者负责恢复堆栈的意思了,函数test是被调用者,所以负责把堆栈加8,call 那里是系统自动恢复的

     

    push eax                ;ESP-4=396->里面保存了eax的值3101

                            ;上面已经看到了eax保存着返回值,我们要把它传给printf也是通过堆栈传递       

    push offset szTextFmt   ;ESP-4=392->里面保存了szTextFmt的地址 也就是C里面的指针 实际上没有什么把字符串传递的,我们传的都是地址

                            ;无论是在汇编或所以在汇编里没有什么字符串类型 用最多的就是DWORD。嘿嘿游戏里面传递参数 简单多了

    call printf             ;ESP-4=388->里面保存了下一条指令的地址

    add  esp,8              ;ESP+8=400 恢复了调用printf前的堆栈状态

                            ;上面说了由于printf后面参数是:VARARG 这样的类型是有调用者恢复堆栈的 所以printf里面没有retn 8之类的指令

                            ;这是由调用者负责清栈 main是调用者 所以下面一句就是 add esp,8 把堆栈恢复到调用printf之前

                            ;call printf那里的压栈 是由系统做的 恢复的工作也是系统完成 我们不用理 只是知道里面保存是返回地址就够  

     

                          ;

    ret                     ;main 函数返回 其他的事情是系统自动搞定 我们不用理 任务完成

     

     

     

     

     

     

     

     

     

     

     

     

     

    动态分析技术:

    OD调试器

  15. 使用符号库(lib),可以让Od以函数名显示DLL中的函数,例如MFC42.DLL是以序号输出函数的,要让以函数名显示相关输出函数。加载方法单击菜单“Debug/Select import libraries”来打开导入库窗口。
  16. OD附加到一个正在运行的程序上,附加后程序会停在NTDLL.DLLDbgBreakPoint处。然后安shift+F9继续运行来调试。如果附加不成功,可以巧妙利用OD的即使调试器功能来调试。
  17. 调试隐藏进程:用类似冰刃的程序得到隐藏进程PID,然后在控制台窗口用-p参数附加即可,注意PID是十进制 c:\ollydbg.exe –p pid
  18. 如果附加不成功 可以利用OD即时调试器 例子:运行A.exe,其会调用Bexe,此时用OD附加B.exeOD会无响应。解决办法:在“Options/Jus-in-time”中设置OD为即时调试器,将B.exe的入口改成CC,INT3指令,同时记下原指令。运行A.exe,其调用B.exe,运行到INT3指令会导致异常,OD会作为即时调试器启动并加载B.exe,此时再将INT3指令恢复原指令,继续调试。

  19. CTRL+N可以打开当前领空的函数输入表。进入一个CALL之后 CTRL+F9 执行到返回该领空的调用该CALL的空间。按ALT+ F9可以退出DLL调用的领空到本程序调用该CALL的空间.
  20. INT3断点,直接按F2下断,其实就是CC指令,机器码是CCH。不过是被OD隐藏了,缺点是有些程序会检测防范API被下断,检查API的首地址是否为CCH,解决办法:断点下在函数内部或底部 或者不在入口下断
  21. 硬件断点,跟DRx调试寄存器有关系 总过有8 DR0-DR3:调试地址寄存器,保存需要监视的地址,如设置硬件断点。 DR4—DR5:保留。DR6:调试寄存器组状态寄存器。DR7:调试寄存器组控制寄存器。硬件断点只用DR0-DR3 4个寄存器 所以只能设置4个硬件断点。
  22. 内存断点可以一次性的 查看ALT+M 在访问上设置断点,执行中断后,断点就被删除。
  23. 消息断点:运行程序之后 在操作窗口之后,按ALT+W 就会显示窗口各类参数,比如BUTTONEDIT 在上面右键之后 选择想要断的消息。注意:断到消息地址之后 不能用ALT+F9或者CTRL+F9执行返回 要用到一次性内存断点之后,系统底层就会通过它返回到程序领空
  24. 条件断点根据条件来设置断点 用命令行bp eax == 0400000 或者用shift+F2 在代码上输入命令
  25. 按存储器条件中断 位于《加密与解密 第三版》 P37 例如输入命令 bp CreateFileA,[STRING [esp+4]] == “c:\\1212.txt
  26. 条件记录断点看P38
  27.  

     

     

posted on 2010-06-20 01:27  龌龊龙  阅读(1014)  评论(0编辑  收藏  举报

导航