《加密与解密》 笔记

 

 

汇编基础:

二进制左移一位 相当于原来的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注册表:

主键类型

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

注册表相关函数: 包含于ADVAPI32.DLL

打开子键

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

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

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

REGSAM samDesired,//存取掩码

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

);

RegQueryValueEx 获取一个项的设置值

 

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

RegOpenKey() RegOpenKeyEx() 打开想要操作的主键获得一个句柄 将句柄传递给 RegQueryValueEx(),RegSetValueEX() 等函数来读写相应的键值 操作完毕后用 RegCloseKey() 关闭先前获得的句柄

保护模式简介:

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用于系统。

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

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

保护模式的权限级别(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调试器

使用符号库( lib ),可以让 Od 以函数名显示 DLL 中的函数,例如 MFC42.DLL 是以序号输出函数的,要让以函数名显示相关输出函数。加载方法单击菜单“ Debug/Select import libraries ”来打开导入库窗口。 OD 附加到一个正在运行的程序上,附加后程序会停在 NTDLL.DLL DbgBreakPoint 处。然后安 shift+F9 继续运行来调试。如果附加不成功 , 可以巧妙利用 OD 的即使调试器功能来调试。 调试隐藏进程:用类似冰刃的程序得到隐藏进程 PID ,然后在控制台窗口用 -p 参数附加即可,注意 PID 是十进制 c:\ollydbg.exe –p pid

如果附加不成功 可以利用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指令恢复原指令,继续调试。

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

 

 

 

转载于:https://www.cnblogs.com/136700948/archive/2010/06/20/1761136.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值