提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一. 壳(重点在4.)
- 加壳加壳的全称应该是可执行程序资源压缩,是保护文件的常用手段。加壳过的程序可以直接运行,但是不能查看源代码。要经过脱壳才可以查看源代码。加壳是利用特殊的算法,对EXE、DLL文件里的资源进行压缩、加密。类似WINZIP 的效果,只不过这个压缩之后的文件,可以独立运行,解压过程完全隐蔽,都在内存中完成。它们附加在原程序上通过Windows加载器载入内存后,先于原始程序执行,得到控制权,执行过程中对原始程序进行解密、还原,还原完成后再把控制权交还给原始程序,执行原来的代码部分。加上外壳后,原始程序代码在磁盘文件中一般是以加密后的形式存在的,只在执行时在内存中还原,这样就可以比较有效地防止破解者对程序文件的非法修改,同时也可以防止程序被静态反编译
1. 压缩壳
1.1 UPX
- UPX是一个以命令行方式操作的可执行文件程序
- UPX加壳程序的作用就是压缩程序代码实现把可执行文件的体积缩小50%-70%
1.2 UPX工作原理
- 首先将程序压缩:在程序开头或其他地方插入一段代码,将程序的其他地方做压缩
- 当程序执行时,对程序进行解压缩,该功能体现在第一步插入的代码中
- upx可以完成代码的压缩和实时解压执行。且不会影响程序的执行效率。
2. 《逆向工程核心原理》第14章学习
2.1 无损压缩
- 缩减文件的大小,压缩后更容易保管
- 例如zip,rar
2.2 有损压缩
- 损失一定信息,换取高压缩率
- 例如jpg,mp3,mp4
- 不能完全恢复
2.3 运行时压缩器

- 把普通文件创建成运行时压缩文件的实用程序即压缩器(packer)
- 压缩器缩减文件大小,且隐藏文件内部的代码与资源
- 例如:UPX,ASPack
- 经反逆向技术特别处理的压缩器称为保护器
- 防止破解,保护代码资源,应用防逆向技术,调试起来困难
- 例如ASProtect,Themida
- 这里举例经过upx压缩的文件

3. 《逆向工程核心原理》第15章学习
3.1 调试upx压缩的notepad程序
- notepad.exe的EP代码

- notepad_upx.exe

3.2 跟踪Upx文件
- 法则:遇到loop循环时,先了解作用再跳出
- 下为跟踪命令

- 步过命令,则开始跟踪代码,按步进命令则停止跟踪
3.2.1 循环1
- 循环内容是:从EDX中读取一个字节写入EDI,EDI所指的地址是第一个节区(UPX0)的起始地址,仅存在于内存中的节区,内容全部为NULL
- 遇到这样的循环,在循环后设置断点F2,按F9跳出循环
3.2.2 循环2
- 正式的解码循环即解压缩循环
- 从ESI所指的第二个节区(UPX1)依次读取值,经过适当运算解压缩后,将值写入EDI所指的UPX0,指令如下:

3.2.3 循环3
- 该段循环用于恢复源代码的call,jmp指令的的destnation地址
3.2.4 循环4
-
设置IAT的循环
-
notepad.exe全部解压缩完成后,应该将程序的控制返回到OEP处

-
要跳转到的目标地址即为源程序的ep地址
-
popad与pushad对应,用来将寄存器恢复原状
3.3 查找UPX OEP的方法
3.3.1 在popad指令后的jmp处设置断点
- UPX压缩器特征之一是,其EP代码被包含在PUSHAD与POPAD中间,跳转到OEP的jmp指令紧跟在POPAD后

- 只要设置好断点,运行后就可以直接找到OEP
4. 《加密与解密》第16章学习----脱壳技术
4.1 基础知识
- IAT:(Import Address Table),导入地址表。由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL中。当PE文件被装入内存的时候,Windows装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成。其中导入地址表就指示函数实际地址。 多数加壳软件在运行时会重建导入地址表,因此获取加壳程序正确的导入地址表也是手动脱壳操作中的一个关键问题。
4.1.1 壳的加载过程
- 壳比源程序代码更早的获取控制权,壳修改了源程序执行文件的组织结构,不会影响源程序的正常运行
- 1.保存入口参数:加壳程序在初始化时保存各寄存器的值,待外壳执行完,恢复各寄存器的值,最后跳到源程序执行,Pushad,popad,pushfd,popfd
- API(Application Programming Interface,应用程序接口)函数是一些预先定义的函数。操作系统除了协调应用程序的执行、内存分配、系统资源管理外,同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务是一个函数),可以帮助应用程序达到开启视窗、描绘图形、使用周边设备的目的。
- 2.获取壳本身需要使用的API地址:记三个函数:

- 3.解密源程序各个区块的数据:程序执行时,按区块解密
- 4.IAT初始化:加壳时构造了自建输入表,并让PE文件头数据目录表中的输入表指针指向自建的输入表,PE装载器填写自建输入表,程序的原始输入表被外壳变形后存储,外壳程序将变形输入表的结构扫描,获取DLL引入函数的地址,并填入自建IAT中
- 5.重定位项的处理:exe文件,壳会删除源文件中保存重定位信息的区块;dll文件,壳会保留,因为操作系统不能保证dll每次运行时都有相同的基地址,需要重定位***(之后补充一下)***
- 6.Hook API:在程序文件中,输入表的作用是让操作系统在程序运行时将API实际地址提供给程序,在第一行代码执行前就完成了,壳修改源程序输入表后,填充表中数据,填充Hook API,就获取程序的控制权
- 7.跳转到程序原入口点OEP:壳在此将控制权交给源程序
4.1.2 手动脱壳
- 一,查找真正的程序入口点;二,抓取内存映像文件;三,重建PE文件
4.1.3 寻找OEP
第一种—根据跨段指令寻找OEP----单步跟踪法
- 加壳后,多了一个区块,就是外壳,相当于一个文件加载器,外壳在程序执行时,拿到控制权,获得API地址,解密源程序各区块的数据去填充IAT,然后跳到OEP执行源程序


- 以上为加壳程序的入口点代码
- 外壳第2部分是还原各区块数据,初始化源程序

- ctrl+A重新分析代码
- 外壳初始化过程:使用两次跨段转移指令,第1次从外壳区块跳到外壳第2部分(随机分配的空间),第2次跳到代码块
第二种 —用内存访问断点寻找OEP----二次断点法
-
内存镜像法的原理在于对于程序资源段和代码段下断点,一般程序自解压或者自解密时,会首先访问资源段获取所需资源,然后在自动脱壳完成后,转回程序代码段。这时候下内存一次性断点,程序就会停在OEP处。
-
使用二次断点法追踪OEP的常见步骤:
1、将待脱壳程序载入到OD中,单击OD的“选项”菜单下的“调试设置”命令,在弹出的“调试选项”对话框中切换到“异常”选项卡,勾选该选项卡下的所有复选框,也就是忽略所有异常;
2、按键盘上的“ALT+M”组合键打开OD的内存窗口;
3、在OD的内存窗口中找到“.rsrc”区段,单击该区段后按键盘上的“F2”键在该区段上下一断点;
4、按“Shift+F9”让程序运行到断点处,而后再次打开OD的内存窗口,这次在“.rsrc”区段上面的“.code”区段(有的时候就是“.text”)上下一个断点;
5、按“shift+F9”让程序运行到第二次下的断点处,然后单步跟踪既可以来到OEP。
第三种—栈平衡原理----ESP定律法
-
通常用pushad popad来保存恢复现场,加壳程序遵守栈平衡原理

-
脱壳时,根据栈平衡原理对esp设断寻找oep
-
使用ESP定律追踪OEP的常见步骤:
1、将待脱壳程序载入到OD中,开始就按键盘上的“F8”键单步跟踪一步,这时如果看到OD右边的寄存器窗口中的ESP寄存器的值有没有变为红色,如果发现ESP寄存器的值变为红色,执行第2步;
2、在OD的命令行窗口中执行命令hrXXXXXXXX,xxxxxxxx就是变为红色的ESP寄存器的值,在输入命令之后,一定不要忘记按键盘上的回车键;
3、按键盘上的“F9”键让程序运行起来;
4、使用单步跟踪的方法跟踪到OEP即可。
4.1.4 抓取内存映像(转存)
- 即把内存指定地址的映像文件读出并保存
- dump程序最佳时期是在程序加壳之前的OEP位置
- 用OD自带的dump插件dump出来
4.1.5 重建输入表
- 可以手动,但一般用Import REC直接重建
二. 新年快乐.exe
2.1 手动脱壳
-
查壳,发现UPX壳
-
拖进OD尝试手动脱壳
-
用esp定律找到OEP

-
设断点,F9运行到此,去断点,步进,得到OEP附近

- dump下来,由于我的OD没有重建输入表,所以自己用Import REC重建一下
- 尝试重建一下输入表(记得先运行源加壳程序)

- 真友好,很顺利的找到
- 首先我们填入正确OEP的RVA,然后点击获取输入表
- 然后添加区段,就是将重建的输入表添加进程序中的空白区段
- 最后修复转存文件
- 修复后,发现一开始的文件变大了4KB
- 其实也间接的证明了我们的UPX壳将源程序压缩了4KB
- 拖入IDA
- 找到main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
char Str2[14]; // [esp+12h] [ebp-3Ah] BYREF
char Str1[44]; // [esp+20h] [ebp-2Ch] BYREF
sub_401910();
strcpy(Str2, "HappyNewYear!");
*(_WORD *)Str1 = word_40306B;
memset(&Str1[2], 0, 0x1Eu);
printf("please input the true flag:");
scanf("%s", Str1);
if ( !strncmp(Str1, Str2, strlen(Str2)) )
return puts(aThisIsTrueFlag);
else
return puts(Buffer);
}
- 函数很简单
- 一个比较函数,HappyNewYear!就是flag
- 成功!!!
2.2 工具脱壳

- 再查壳,发现为32位无壳文件
- 拖入IDA分析,同上
三. maze.exe
-
查壳,32位有壳,UPX3.95
-
工具去壳
-
再用IDA反编译
-
但是找到关键字符串后,发现没有交叉引用的函数
-
根据提示,猜想有花指令

-
这是主函数
-
jnz这句没用,就是跳到下一行
-
call 这句(0x40102e)选中按d转换成数据,下面几行数据选中按c转换成代码,
-
最后将红色的汇编指令选中按p转换成函数,
-
main函数终于出来了
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // ecx
int v5; // [esp-4h] [ebp-28h]
int i; // [esp+10h] [ebp-14h]
char v7[16]; // [esp+14h] [ebp-10h] BYREF
sub_401140(aGoThroughTheMa);
v5 = scanf("%14s", v7);
if ( (v3 ^ v5) == v3 )
JUMPOUT(0x40102E);
for ( i = 0; i <= 13; ++i )
{
switch ( v7[i] )
{
case 'a':
--*(_DWORD *)asc_408078;
break;
case 'd':
++*(_DWORD *)asc_408078;
break;
case 's':
--dword_40807C;
break;
case 'w':
++dword_40807C;
break;
default:
continue;
}
}
if ( *(_DWORD *)asc_408078 == 5 && dword_40807C == -4 )
{
sub_401140(aCongratulation);
sub_401140(aHereIsTheFlagF);
}
else
{
sub_401140(aTryAgain);
}
return 0;
}
- 结合题目即swad,很明显是个迷宫题
- 先找到迷宫
- +** ****** **** ******* F**** **************
- 接下来走迷宫,找到路径的flag
- flag{ssaaasaassdddw}
四. 花指令
- 花指令一般被分为两类,被执行的和不会被执行的。
- 常用的两类反汇编算法:
1.线性扫描算法:逐行反汇编(无法将数据和内容进行区分)
2.递归行进算法:按照代码可能的执行顺序进行反汇编程序。
4.1 花指令实现
#include<stdio.h>
int main(){
int a = MessageBox(NULL,"Hello","main",MB_OK);
int b,c,d,e;
//这部分为花指令部分
__asm{_emit 0xe8}
//花指令结束
b=1;
c=2;
d=3;
e=4;
return 0;
}
4.1.1 jmp指令跳过
__asm{
jmp label1
db junkcode
label1:
}
- ida采用的是递归扫描算法,故可以正常识别
4.1.2 多层jmp跳过
start://花指令开始
jmp label1
DB junkcode
label1:
jmp label2
DB junkcode
label2:
jmp label3
DB junkcode
label3
- 无济于事
4.1.3 互补条件
_asm{
jz label1
jnz label1
db junkcode
label1:
}
- IDA成功被绕过
4.1.4 恒成立条件跳转
__asm{
push ebx
xor ebx,ebx
test ebx,ebx
jnz label1
jz label2
label1:
_emit junkcode
label2:
pop ebx//需要恢复ebx寄存器
}
__asm{
clc
jnz label1:
_emit junkcode
label1:
}
4.1.5 call ret组合构造
__asm{
call label1
_emit junkcode
label1:
add dword ptr ss:[esp],8//具体增加多少根据调试来
ret
_emit junkcode
}
call指令:将下一条指令地址压入栈,再跳转执行
ret指令:将保存的地址取出,跳转执行
文章详细阐述了加壳技术,如UPX的工作原理和压缩壳的类型。介绍了如何通过逆向工程找到并脱掉UPX壳,包括手动和工具辅助的脱壳方法,以及如何寻找OEP。此外,还讨论了花指令的概念和实现技巧,用于绕过反汇编器的解析。

3135

被折叠的 条评论
为什么被折叠?



