加密与解密笔记

来源:互联网 发布:ripper算法是什么 编辑:程序博客网 时间:2024/05/16 17:07


 

第二篇:  调试篇

动态分析技术中最重要的工具就是调试器,分别为用户模式和内核模式. 前者工作在Ring 3级, 如OllDbg,VC++自带的调试期等

内核模式调试器就是能调试操作系统内核的调试期,他们处于CPU和操作系统之间工作在Ring 0级,如 SoftICE 等

 

2.1 OllyDbg

简称OD , 是由 Oleh Yuschuk (www.ollydbg.de) 编写.  结合了动态调试和静态分析

2.1.1  界面

窗口依次如下:

Log 日志, Executable modules 可执行模块 , Memory 内存  , Threads 线程 ,  Windows ,  Handles 句柄,  CPU , Patches 补丁

Call stack 调用堆栈  ,, BreakPoints  断点,  References  参考文献,  Run trace 运行跟踪  Source 资源 等


 2.1.5  断点

1. INT3断点

当执行一个INT3断点时,此地址的内容被调时期用INT3指令替换,此时OD将INT3隐藏了,现实出来的人然是下断点前的指令

由于是修改机器码为CC,所以缺点是会被软就爱你检测到,例如防范API下断点,检测API首地址是否为CC, 检测源码如下

 

 FARPROC uaddr;
 BYTE mark =0;
 (FARPROC&) uaddr = GetProcAddress(LoadLibrary(L"user32.dll"),"MessageBoxA");
 mark = *((BYTE*)uaddr);  //取函数第一字节
 if(mark == 0xcc)   //发现断点

 

这样程序编译后对 MessageBoxA下断点,程序就会发现自己被跟踪,躲过方法是将断点下载程序内部或尾部,例如将断点设

在函数入口的下一行.

 

2.  硬件断点

硬件断点和DRx调试器有关,

DRx 调试寄存器总共有8个,从 DR0 到 DR7 ,每个特点如下:

DR0~3 : 调试地址寄存器,保存需要监视的地址,如设置硬件断点

DR4~5 : 保留,未公开作用

DR6 : 调试寄存器组装台寄存器

DR7: 调试寄存器组控制寄存器

硬件断点的原理是使用4个调试寄存器, DR0~3 来哦设定底子好,以及 DR7 设定状态,因此只能最多设4个断点

OD 的F4功能也是利用调试寄存器原理实现的

 

3. 内存断点

 原理是对所设地址 设为不可访问/不可读写属性,,内存断点会降低Od速度,因为每次异常都要通过比较异常地址是否

为断点地址。

 

4.内存访问一次性断点

windows对内存使用段页式的管理,每个断都有不可访问,读,写,执行属性,在这里下断点,是对整个内存快设置

这个断点是一次性的,

 

5.消息断点

在调试时没有合适的断点,可以尝试消息断点,

 

6.条件断点

(1) 按寄存器条件中断

选中某一行反汇编代码,按下快捷键是 Shift + F2 ,在条件框内输入表达式 "eax==0400000" . 这样,程序执行到这段代码时

如果eax 值为400000h , OD将中断

 

(2)按存储器条件中断

这里以CreateFileA函数为例,运行实例调用查找CreateFileA设断,就在查到的那里,不是程序领空,断下后即可在堆栈窗口

看到函数原型,和实参, 堆栈窗口右键用esp查看,即可知道此函数对应esp的偏移数

 

7.条件记录断点

除了具有条件断点的作用,还能记录断点处函数表达式或参数的值。也可以设置通过断点的次数

方法还是如上,但是是调用 SHIFT+F4,条件跟上面的条件断点一样, 可以添加说明, 和一个 记录的值,这个值是什么类型

必须在下面的解码表达式值列表框中选正确。 这样断下来后,就可以在 日志数据窗口看到了

 

2.1.6  插件

1.常用插件介绍

(1)命令行插件

? 表达式                                计算表达式的值,如: ? 34*45-4

D(DB,DW,DD) 表达式           查看内存数据,如 D 40000, D esp+c

BP 表达式 [条件式]               设置断点, 如  bp GetDigItemTextA

Hw  表达式                            设置硬件写断点

更多功能查看帮助文档

 

2.1.10 常见问题

1.乱码问题,

出现乱码说明OD将这一段读成数据了,没有进行反汇编识别, 按下 Ctrl +A强迫Od重新分析代码。如果还不行就删除对应的 .udd

2.如何快速回到当前领空

寄击寄存器面板的EIP

3.OD如何修改EIP

首先将光标移到需要的地址上,右键 此处为新EIP

5.为什么删除了断点,重新加载时候还会出现,

设置配置文件 ollydbg.ini  Backup UDD files=1

6.反汇编窗口中 输入会变代码时 输入 push E000会提示未知标示符

因为OD不能正确识别 E000 的E是字母还是数字,解决办法前面加0 ,即 push 0e000

7.OD假死

调试加壳程序时,会出现假死, 修改配置文件 Restore windows =0 即可

8.如何微调窗口显示

Ctrl + 上  或者下

9.执行复制到可执行文件时,出错  Unable to locate data in executable file(找不到可执行文件中的数据)

这个要修改的地方不在 RawSize范围内,修改PE文件,使 RawSize = VirtualSize

10.能否把CALL调用成函数名形式

比如 call 401496 ,加入 401496是 amsg_exit 函数, 将光标停在 401496,单击 Shift+; ,出来一个标签框,输入字符即可

 

 3:静态分析技术

 3.3 可执行文件的修改

IDA适合分析文件,若要对文件编辑修改,则需要专门的十六进制工具。

exWorkshop 提供了文件比较功能,  WinHex 可以查看内存映像文件,Hiew 可以在会变状态下修改代码

1. Hiew 反汇编的语法如下:

“byte/word/dword/pword/qword/tbyte” 可简写成 "b/w/d/p/q/t"

所有数字都是十六进制,所以 h 操作符可选的

以A,B,C,D,E,F开头的十六进制数据,必须加前缀0

无条件跳转 jmp xxx 将转换成 0E9 xx xx 形式, 因此近转移 jmp (0EB)  需按如下形式 : jmp short xxx, 或 jmps xxx

远转移形式 :  jmp xxx, 其中 xxx 是文件偏移地址

 

反汇编中 按F5跳转, 输入的是 偏移地址,如果输入 虚拟地址,要在前面加上点号  .40109E

 

4.逆向分析技术

4.1  启动函数

Windows程序执行并不是从WinMain函数开始的,首先被执行的十 启动函数相关代码,启动代码完成初始化进程在调用WinMain

所有c/c++运行时启动函数的作用基本是相同的, 检索指向新进程的命令行指针,检索指向新进程的环境变量指针,

全局变量初始化,内存堆栈初始化。所有初始化操作完毕后,启动函数就调用应用程序进入点函数

 GetStartipInfo(&StartupInfo)

Int nMainRetVal = WinMain(GetModuleHandle(NULL),NULL,pszCommandLineAnsi,(StartupInfo.|dwFlags&STARTF_USESHOWWINDOWS)?StartipInfo.wShowWindow:SW_SHOWDEFAULT);

 

4.2.1  函数的识别

编译器都是用 CALL 和 RET  指令来挑用函数和返回调用位置

CALL 指令与跳转指令类似,不同的是, CALL保存返回信息,即将气候的指令地址压入对战的顶部,当遇到 RET 时返回到这个地址处

 

4.2.2  函数的参数

函数传参有3种,堆栈方式,寄存器方式,通过全局变量进行隐含参数的传递

 1堆栈传参

是一种先进后出的存储区,栈顶指针esp指向堆栈中第一个可用数据项。调用函数时一次压栈,最后栈平衡

_cdecl 是c和c++程序默认约定,还有MFC也是这种调用,从右到左顺序入栈,调用者执行栈平衡

stdcall 调用约定是 Win32 API函数采用的方式,着红方式 从右到左顺序入栈,但是由被调用方栈平衡,由于知道参数个数

所以函数可以在返回前用ret N 来来清理堆栈,也有些函数比如 wsprintf用的 _cdecl 传参

 

非编译器优化的传参过程:

1.依次传参,每一次push只能是4字节操作

2.参数传完在将call的下一条代码地址 压栈

3.函数中 push ebp保存栈底,

4.mov ebp.esp  将栈底用来寻参

5执行函数体,如果过程中用到局部变量,则需要 sub esp , N , 来申请局部变量空间,用完后 add esp ,N

6.最后 ret N  来平衡栈

此外还有一组指令, enter 和 leave  .他们进行堆栈的维护,

enter 作用是 push ebp / mov ebp,ep / sub esp ,xxx

leave 则完成 add esp,xxx/ pop ebp 的功能

例子:

enter xxxx,0    ;  在地址空间xxxx存放局部变量 0

....

leave     ;恢复现场

 

 2.寄存器传参

虽然这个方式没有一个标准,但是大多数编译器都遵循相应的规范,即 _fastcall 规范, 顾名思义,特点就是快,因为是寄存器

不同编译器实现_fastcall稍有不同,图

 

 vs c++ 编译器采用_fasrcall规范传递参数时,最左边的2个不大于4字节,(DWORD)的参数分别用ecx,和 edx 寄存器,

当寄存器用完后,就要使用堆栈,其余参数任然按从右到左顺序入栈,由被调用函数内清理堆栈

注意,浮点值,远程指针和_int64 类型总是通过对咱来传递.

 

Borland Delpgi/C++编译器采用的_fastcall 规范使时,最左边的三个不大于4字节的参数分别在 eax,edx,ecx,寄存器用完后,

多余参数按照从左至右的 PASCAL方式来压栈.

 

Watcom C编译器总是通过寄存器来传递参数,其严格为每一个参数分配一个寄存器,默认第一个参数用eax,第二个edx

第三个ebx,第四个ecx,如寄存器用完,再用噢乖堆栈传递参数。 Watcom C可以由程序猿制定任意一个寄存器传递参数

 

VC++传参例子:

 int    __fastcall add(char,long,int,int)

add(1,2,3,4)

参数压栈时,从右往左,

push 4

push 3

还剩2个参数时,从右往左第三个参数 2 用edx寄存器

mov  edx, 2

最后一个参数用 ecx 寄存器,由于类型是单字节,8位大小,所以

mov cl ,1

 

call 函数之后,在函数体内

会先开辟8个字节的大小,作为局部变量,用来保存2个寄存器的值

 

thiscall 调用规范也用到了寄存器,这是c++中非静态类型成员函数的默认调用约定,每个对象的每个函数隐含接受

this参数,采用thiscall约定,参数从右道宗顺序入栈,由被调用函数栈平衡 ,只通过ecx寄存器传递额外的this指针

 

3. 名称修饰约定

 在c++中为了允许操作符冲在和函数重载,c++编译器往往按照某种规格改写入口点的符号名这项技术称为名称改编或

名称修饰,

VC++ 中,C编译时,函数名修饰约定:

_stdcall 调用约定在输出函数名前加上一个下划线,后面在加@符号和其参数的字节数, ,格式为   _函数名@参数字节数

_cdecl 调用约定仅在函数名前加下划线   格式为  _函数名

_fastcall 调用约定在函数名前加 @ 符号,+  函数名 + @ ,和其他参数的字节数, :  @函数名@参数字节数

 

VC++中, C++编译时函数名修饰规则

_stdcall 调用以 ?标识函数名的开始,后跟函数名,再加 @@YG 标识参数表开始,后跟参数表。参数表第一项为该函数返回

值类型,气候依次为参数的数据类型,指针标识在其所指数据类型前,参数表后 @Z结束,如果函数无参数 则 Z结束

?函数名@@YG*****@z  或  ?函数名@@YG*XZ

_cdecl 调用约定只是把上面的开始标识 @@YG 改成 @@YA

_fastcall 调用约定只是把上面的开始标识 @@YG 改成 @@YI

 

4.2.3 函数的返回值

1.用 return 操作符返回值

一般情况下,函数的返回值在 eax寄存器中返回,如果超过了eax寄存器容量,其高32位就会放到 edx寄存器

 

2.通过参数按传引用的方式返回值

 

 

 4.3  局部变量

局部变量是函数内部定义的变量,只有在函数内部才使用.从汇编角度来看,局部变量就是在堆栈中进行分配,函数执行完后释放

或者直接把局部变量放在寄存器中

1.利用堆栈存放局部变量

 一般情况下, 程序用 sub esp,8 为局部变量分配空间, 用 ebp-xxxx 寻址局部变量,  ebp+xxx 寻址函数参数

局部变量分配与释放有3种形式

1. sub esp,n  ...  add esp,n     2. add esp,-n ..  sub esp,-n  3.  push reg ... pop reg

局部变量的起始值是随机的,是其他函数执行后留在堆栈中的垃圾数据,因此要进行初始化,有2种方法

1.mov [ebp-04] , 5                 2.  直接 push 5   将值压栈

 

除了堆栈占用了2个寄存器外,编译器就将剩下的6个通用寄存器经可能的存放局部变量.

 

4.3.2 全局变量

全局变量存放在全局变量内存区,而不惧变了则存在于函数的堆栈区。大多数程序中,常数一般放在全局变量中

在汇编代码中识别全局变量比其他结构容易,全局变量通常位于数据区块 .data 固定在一个地址上,程序访问全局变量时

会直接用固定的硬编码的地址直接对内存寻址,比如

mov eax , dword ptr [4084c0h]

如果一个变量被放到 只读区块 .rdata  那么这是一个常量

 

4.3.3  数组

数组是相同数据类型元素的集合,他们在内存中按照顺序连续存储.会变下访问数组一般是 基址加上某变量实现的

 

4.4 虚函数

c++的对象模型核心概念并不多,最核心的就是虚函数. 虚函数在程序运行时刻定义的函数,虚函数的地址不能

在编译时候确定,只能在调用即将进行之前加以确定。 所有虚函数引用通常都放在一个专用数组-- 虚函数表中

每个至少使用一个虚函数对象里面具有虚函数表指针。

 

4.5 控制语句

 4.5.1 if- then - else

在汇编代码中,整数用cmp比较,浮点值用 fcom,fcomp 比较

一般形式如下

cmp a,b

jz[jnz] xxx

许多情况下,编译器用test 或 or 之类的短逻辑指令替换cmp

test eax,eax //如果eax为0,其逻辑与运算结果为0,就设置ZF为1,否则为0

 

4.5.2 switch -case

switch 实质就是多个 if then 语句嵌套组合.

 

4.5.3 转移指令机器码的计算

根据转移的距离,转移指令有以下类型:

短转移 Short Jump : 无条件转移和条件转移的机器码都是2个字节.转移范围是 -128~ +127 字节

长转移 Long Jump : 无条件转移的机器码是5个字节,条件转移的机器码是6个字节.这是因为条件转移要用2个字节表示转移类型

如 je, jns ,其他4字节表示转移便宜量. 无条件转移近用到 一个字节 jmp ,其他4字节用作偏移

1.短转移的机器指令形式

EIP += 位移量 ,然后跳转到 EIP

也就是说 位移量  =  目的地址-其实地址 - 跳转符本身长度

 

 

4.7 数学运算符

4.7.1  整数的加法和减法

一般情况下用 add 和 sub 指令, 优化后,比较喜欢用 lea 指令代替 add 和 aub  . lea 允许用户在一个时钟周期内完成

c = a+b+78h 计算,其中 a,b,c 都是在寄存器的情况下才会有效. 会编译成 lea c, [a+b+78]

 

4.7.2 整数的乘法

乘法运算符一般编译成 mul , imul 指令,这些指令运行速度慢,所以尽可能的用其他算数运算来完成

 

4.7.3 整数的除法

一般指令为 div, idiv 指令,代价非常高,大概比乘法多消耗10倍的CPU时钟周期。

如果被除数是未知数,用div,如果除数或者被除数有一个是常量,那么编译器就会使用技巧有效的实现除法

 

4.8 文本字符串

4.8.3 字母大小写转换

大写字母的ASCII码范围 41h ~ 5ah ,消协字母是 61h ~ 7ah,他们之间的转换就是 ASCII码 值加减20

 

 

第三篇:解密篇

5.1.2 如何攻击序列号保护

通常都是在编辑框中输入注册码,软件需要调用一些标准 API获得编辑框内容, 利用调试期提供的针对API设断点

GetWindowsTextA(W)  ,  GetDlgItemTextA(W)  ,  GetDlgIntemInt 等API。程序判断完后一般会显示注册是否正确

MessageBoxA(W) ,  MessageBoxExA(W)  ,  DialogBoxParamA(W) ,  CreateDialogIndirectParamA(W),DialogBoxIndirectParamA(W)

CreateDoallogParamA(W), MessageBoxIndirectA(W) , ShowWiindow 等

 

另一种办法就是跟踪程序启动时候对注册码的判断,从而确定以注册版的模式工作还是其他。

如果序列号放在注册表中,可以用 RegQueryValueExa(W) : 如果序列号放在 INI 文件中, 可以用 GetPrivateProfileStringA(W)

GetProfilestringA(W , GetPrivateProfileIntA(W) ,  GetProfileIntA(W 等函数

 

1. 数据约束性的秘诀

在明文比较注册码的保护方式中,这个概念是 +ORC 提出的,因为注册码大多数都是在一个小的堆栈区域,所以正在的注册码

距离用户输入的注册码 存在 正负90h 字节的地方

在 OD中 , 内存窗口 搜索输入的注册码,在他的上下不远就可以看到正确的注册码了

 

 

5.2  警告窗口

创建窗口的函数有 MessageBoxA  , MessgeExA ,  DialogBoxParamA , ShowWindow , CreateWindowExA 等

 

5.3 时间限制

5.3.1 计时器

//申请一个计时器,并指定计时器的时间间隔,还可提供一个处理计时器超时的回掉函数,当计时器超时,

系统会发送申请计时器的窗口 WM_TIMER 消息,或调用者提供的回调函数

UINT SetTime (HWND hwnd ,UINT nIDEvent ,, UINT uElapse  ,TIMERPROC lpTimerFunc)

参数表: hwnd 窗口句柄,  nIDEvent 计时器标识 , uElapse 指定间隔  , TIMERPROC 回调函数


回调函数如下格式 :

void CALLBACK TimerProc (HWND hwnd, UINT uMsg , UINT idEvent , DORD dwTime);

相对的,用KillTime() 函数销毁计时器


2.高精度多媒体计时器

多媒体计时器的精度最高达到一毫秒

MMRESULT timeSetEvent(UINT uDelay , UINT uResolution , LPTIMECALBACK lpTimeProce ,DWORD-PRT deUser

Uint fuEvent);


3.GetTickCount()返回系统成功启动所用毫秒数。将返回的2次相减,可知道程序运行时间

4.timeGetTime() 


 5.3.2    时间限制

首先安装软件在安装时候取得系统日期,或主程序第一次运行时候,记录在注册表或者某文件或者扇区

程序每次运行时候都要取得系统日期,并进行比较,软件关闭时候会取得系统时间,

下一次启动时候比较上一次启动的时间,就可知道系统时间是否被修改

一般取得时间的API  GetSystemTime , GetLocaTime GetFileTime

 

5.4  菜单功能限制

BOOL EnabkeMenultem (hwnd hMenu , uint uIDEnableItem ,UINT uEnable)

允许或禁止菜单条目

hMenu : 菜单句柄  uIDEnableItem : 菜单条目标识符 uEnable : 控制标志 有 MF_ENABLED (允许 0h) ,

MF_GRAYED(灰化 ,1h) , MF_DISABLED (禁止, 2h) , _MF_BYCOMMAND 和 MF_BYPOSITION

返回值: 菜单以前的状态,如果菜单项不存在返回 FFFFFFFFF

 

BOOL EnableWindow (HWND hwnd , BOOL bEnable)

允许或禁止窗口

 

5.1  KeyFile 保护

FindFirsFileA  确定注册文件是否存在

CreateFileA    确定文件是否存在: 打开文件获取句柄

GetFileSize , GetFileSizeEx   获得注册文件大小

GetFileAttributesA  GetFileAttributesExA  获得注册文件的属性

SetFilePointer , SetFilePointerEx  移动文件指针

ReadFile   读取文件内容

 

 

 

 

6.加密算法

6.1 单向散列算法

单向散列函数算法也称 Hash(哈希)算法,是一种将任意长度的消息压缩到某一固定长度(消息摘要)的函数(该过程不可逆)

常见的 散列算法有 MD5 , SHA  , RIPE-MD , HAVAL , N-Hash 等

 

6.11  MD5

MD5消息摘要算法 (Message Digest Algorithm) 是由 R.Rivest 设计的. 它对输入的任意长度消息进行运算,产生一个 128 位

的消息摘要.

 

1.算法原理

 

填充消息。使其长度与448模512同余 (即 长度 = 4448 mod 512)  也就是说,填充的消息长度比512的倍数仅小64位数。

即使消息长度本身满足上述长度要求,任然需要填充。

填充方法是附加一个1在消息后面,然后用0来进行填充,知道  长度 = 4448 mod 512 . 至少填充1位,至多填充512位

 

添加长度。 在第一步的结果之后附加64位的消息长度.如果填充前消息的长度大于 2的64次方,则只是用其低64位

这时,在添加完填充和消息长度后,最终的消息长度正好是 512的整数倍了。

 

初始化变量。用到4个变量A,B,C,D 来计算消息摘要。这里的A,B,C,D,分别都是32位的寄存器。这些寄存器以下面的

十六进制数支来初始化 A=01234567 , b=89abcdefh , c=fedcba98 , d =76543210

并且在内存中是以低字节在前的形式

 

数据处理。以512位分组为单位处理消息,首先定义4个辅助函数,都是以3个32位的双字作为输入,输出一个32位双字

F(X,Y,Z) = (X&Y) | ((~X)&Z)

G(X,Y,Z) = (X&Z)|(Y&(~Z))

H(X,Y,Z) = X^Y^Z

I(X,Y,Z) = Y^(X|(~Z))

 

 

 

第六篇:脱壳篇

12.2  压缩壳

目前兼容性和稳定性比较好的压缩壳有 UPX, ASPack , PEcompact 等

 

12.2.1 UPX

UPX是一个命令行操作的可执行免费程序,并且开源,格式为 upx[-123456789dlthVL] [-o file] file...

 

12.2.2 ASPack

可以压缩 EXE ,DLL ,OCX 具有很好兼容性

 

12.2 加密壳

加密壳种类比较多,不同壳侧重点不同,有的壳还额外提供注册机制,使用次数,时间限制等。加密壳的另一个特点就是,

越有名的加密壳,研究的人就越多。

 

12.3.1 ASProtect

ASProtect 是一款非常强大的Win32保护工具,这款壳开创了壳的新世代。拥有压缩,加密,反跟踪代码,CRC笑言,和花指令

等保护措施,它使用Blowfish,Towfish ,TEA等强劲算法加密,还有RSA1024作为注册密钥生成器,他还通过API钩子与加壳程序

进型通讯,并且为开发人员提供Sdk,实现加密程序内外结合。

 

12.3.2 Armadillo

Armadillo也称穿山甲,是一款商业保护软件,可以为软件加上种种限制,包括时间,次数,启动画面等

Armadillo 加壳时,会扫描程序,处理标签里的跳转指令,将所有跳转换成 INT3 指令,此时Armadillo运行时,是双进程,子进程

遇到CC异常,就由父进程截获这个异常,计算出跳转指令的目的地址并反馈给子进程,子进程继续运行,这种保护也叫CC保护

 

 

12.4.虚拟机保护软件

许多解释语言,比如Vb的 P-code程序也是一种虚拟机,它将一系列指令解释称 bytecode 字节码 放在一个解释引擎中执行

 

12.4.1 虚拟机介绍

一个虚拟机引擎由编译器,解释其和 VPU Context (虚拟CPU环境)组成,再配上一个或多个指令系统。

虚拟机运作时,先把已知的x86指令根据自定义的指令系统解释称字节码,放在Pe文件中,然后将原处代码删掉,调试着进入

虚拟机后,是非常难于理解源指令的。虚拟机的技术就是以效率换安全,一条原始汇编指令经过Vm处理,会膨胀几十倍

甚至几百倍,所以Vm提供Sdk,使用者只把重要代码用虚拟机保护起来。

 

 

13.脱壳技术

 

13.1.1 壳的加载过程

壳和病毒都需要比原程序代码更早获得控制权。

1. 保存入口参数

加壳程序初始化时保存寄存器的值,外壳执行完毕,在恢复各个寄存器内容,最后调到原程序处执行。

通常用 pushad / popad , pushfd/popfd 指令来保存或恢复现场环境

 

2.获取壳自己所需使用的API地址

一般外壳的输入表只有 GetProcAddress , GetModuleHandle 和 LoadLibrary 这几个函数,甚至只有 Kernel32.dll

如果需要其他函数,则可以通过 LoadLibraryA或 LoadLibraryExA 将 DLL文件映射到进程地址空间,函数返回 HINSTANCE

值用于标识文件映射到虚拟内存地址

以下是一般调用形式:

1.   HINSTANCE LoadLibrary(LPCTSTR lpLibFileName) // DLL 文件名

将DLL文件映射到进程地址空间,返回文件映射的虚拟地址

 

2. HMODULE GetModuleHandle(LPCTSTR lpLibFileName) // DLL文件名

如果DLL已经被映射到调用进程空间,可以调用这个函数获得DLL模块句柄

 

3. FARPROC GetProcAddress(HMODULE hModule , LPCSTR LpProcName) // DLL 模块局并, // 函数名

一旦DLL模块被加载,线程就可以调用这个函数获取输入函数的地址

 

3. 解密原程序各个区快数据

壳处于保护原程序代码和数据目的,一般会讲区快数据加密。壳一般按区块加密的,在解密时把区快数据按照区快定义放在合适内存

 

4.IATA的初始化

IATA的填写,本应由Pe加载器实现,但是加壳时,自己构造了一个输入表,并让Pe头中的输入表指针,指向了自建的输入表。所以

原来Pe的输入表填写,只好由外壳程序实现。外壳要做的就是将这个新的输入表结构从头到尾扫描一遍,对每一个DLL引入的函数

重新获取地址,并填写在IAT表中。

 

5.重定位项的处理

文件执行时将被映像到指定内存地址中,这个初始内存地址称为基址。例如某Exe文件基址为 400000,而运行时系统提供的地址同样

也是400000,这种情况下就不需要重定位,

不过杜宇DLL来说,系统没办法保证每一次DLL运行提供相同的即地址,这样重定位就很重要了,从这点来说加壳DLl的修正比Exe

多了一个重定位表

 

6.HOOK-API

壳一般都修改了原程序文件中德输入表,然后自己模仿Windows系统工作填充输入表中相关数据,在填充过程中,就可以填充

HOOK-API 地址,间接获得程序控制权

 

7.跳转程序原入口点 (OEP)

这个时候就把壳控制权交给原程序,一般在这里都有明显的分界线。当然现在加壳将OEP一段代码搬到外壳程序的地址空间,然后

将这段diamagnetic清楚掉,这种技术成为 Stolen Bytes 。

 

 

13.1.3 手动脱壳

手动脱壳分为3步: 一是查找程序的真正入口点, 二是抓取内存映像文件, 3是Pe文件你重建

13.2 寻找OEP

13.2.2 内存断点访问OEP

外壳首先将原来压缩的代码解压,并放到对应区快上,处理完毕,将调到代码段执行。所以,当对代码段内存设访问断电时候

一定会中断在外壳对代码写入的那句上面

首先对 Text区段下断,然后再对 .data下断,最后在对 TEXT下断,即可到达

 

13.2.3 根据堆栈平衡原理找OEP

PUSHAD 相当于 push eax,ecx,edx,ebx,esp,ebp,esi,edi

POPAD  相反

当运行 pushad后 对 esp 所指的内存下硬件访问断点 hr

然后F9 就理OEP不远

 


13.3.1 Dump 原理

抓取内存映像也称转存,就是把内存制定的地址的映像文件读取出来,用文件形式保存。一般情况下来到oep处在Dump

原理是进程快照获取进程的基本信息,反Dump技术会修改 MODULEENTRY32结构的 modBaseSize字段


13.4 重建输入表



原创粉丝点击