写个壳
[TOC]
已有
1 |
|
目标
1 |
|
注意事项
1 |
|
不能使用多线程来进行解压缩 因为在shell代码中线程化是一个非常糟糕的想法!
基础和高级
一般的加壳都是下面这些步骤
1 |
|
1 |
|
- 加壳的难点在于将数据目录项复制到补丁代码部分,并修正复制后的数据目录项
- 加壳中加密的核心是加密算法
一般壳都是带压缩的壳
压缩是一个比较复杂的过程,对于一个主要功能的加密的壳来说,压缩也有一定的加密效果,如果使用了一些加密库加密,即使你压缩了,会发现加壳后的文件比没加壳之前还要大!
vmp用不好,就变成了第三类壳:增积壳
作用:虽然没有虚拟化保护软件关键函数,但是可以增加软件体积,吓唬破解者,顺便增加检测虚拟机或是调试器附加的功能
很多常见的壳都用汇编写的,确实,汇编确实可以写出很多短小精悍、骚操作的代码,这是C++所没有的,但是C++支持内联汇编,在一定程度上弥补了它的不足。
加密节(除了tls和rsrc)
比如加密压缩的过程中每次可以随机使用不同的加密压缩算法,比如调用rar,zip,upx的压缩算法,比如使用DES、3DES、AES 、 RSA、DSA 、SHA-1、MD5等加密算法……或者这些都随机调用,每次生成算法都不同
压缩
哈夫曼树 upx
到现在我比较想写的是unishox2压缩算法siara-cc/Unishox2: Compression for Unicode short strings (github.com)和base-N压缩算法莫迪克|关于计算机安全的随机帖子 (wordpress.com)
反调试
参考VMP
学保护模式 ——》 为了突破UAC (也能学到反调试)
反调试 –》为了学加壳等
学tenprotect的保护机制 –》反向学习反调试(因为会学到Windbg双机调试的保护、ValidAccessMask清零的保护和DebugPort清零的保护)
对于PE结构的表的获取结构体 还有一种是利用特征码的获取结构体(只能获取某个内核api) 显然就没那么高操作 pe表获取结构体也会引用其中内容。所以pe结构是一个值得反复学习的东西
常规的反调试都是getparentpresent和beginndebuged来获取是否被调试。
2019年有一个文章用的是NtqueryInformationProcess(这个函数可以同时在0环和3环运行)的第二个参数传入ProcessDeubgpory并获取传出的nDebugPort来判断是否被调试
写出了好的反调试方法,可以放在壳代码的各个角落,检测到调试就马上退出程序,多放置几个阴人位置,这样就能增加破解的难度了!
在32位程序中,有人把钩子挂到64位的”ntdll.dll”上,然后来反反调试,可以用crc校验或者检测关键字节来反反反调试
https://www.52pojie.cn/thread-1277269-1-1.html
一般虚拟机沙箱的网卡还有文件默认设置 进程少等特点 可以用来检测一下
30个反调试方法(2016年)
https://github.com/wanttobeno/AntiDebuggers/blob/master/Tencent2016D.cpp
1 |
|
https://github.com/strivexjun/XAntiDebug/blob/master/XAntiDebug/XAntiDebug.cpp
anti-debug-popf
1 |
|
anti-debug-int2d
1 |
|
当然直接ollvm可以直接反调试
加密iat
IAT(导入地址表 import address table)每个元素的地址就是内存窗口的地址
这时候我们就需要去加密这些地址来干扰调试器对winapi的获取
加密原理
1 |
|
hash单向散列(数值加密) 加密api函数的字符串 (因为一些有经验的逆向师会对字符串比较敏感)
众所周知1个字节是8位,这代表他表示2的8次方个数,也就是256种可能,如果我们把它的一个数据代表一个系统中的函数(API),相当于给函数一个序号,那么1个字节就能存储256个函数的信息,那2个字节就能存储2的16次方也就是65536个API函数,这真是大大的好消息, windows系统中的API函数也就几千个,2个字节存储其全部API函数信息真是绰绰有余。
而让这2个字节的数据代表一个函数,这个数据我们称它为Hash值,因此需要设计一个算法。我在这设计是方法是定义一个2字节类型(short)的数据,分别把nHash值先左移11位再右移5位后相加,再加上API函数中一个字符的Ascii码,以此循环遍历完整个API函数的所有字符,得到一个我们需要的Hash值。在之前写壳基础篇中提到过壳代码中的API是动态获取的,那么我们在动态获取的时候使用Hash值更能提高隐蔽性,使破解者不易发现我们所要使用的是哪个函数。
具体Hash加密代码如下:
1 |
|
使用纯汇编来在壳代码中写入解密函数的好处
1 |
|
hash加密(要有密钥的那种)
动态加密
加入动态解密的壳,这无疑是强度较高的壳了,它能够在目标程序运行起来之后,动态的对代码段进行解密。先运行一段代码解密后一部分的代码,然后再运行解密后的代码,可以往复循环,这样破解者只能看见运行着的代码的附近的代码,隔得远的代码处于加密状态,这样就需要花费大量的时间才能破解了,当然想要实现这种高强度,还是需要花费很多时间去设计的,而且要求我们对x86汇编语言有比较深刻理解,
解密代码才是动态解密中的核心点,重中之重。因为加密代码全部去加密就可。解密代码的话就将代码分段分时解密
这里要说一下GetPC技术,GetPC技术翻译为中文也就是获取指针计数器。在x86汇编中实际上就是获取当前代码EIP的技术。我这用的是call 指令,call xxx指令相当于 push 下一行代码的EIP + jmp xxx。 那么我们直接把XXX改为下一行指令的地址就能获取当前EIP
其他操作
在遍历还原导入表时,并没有直接将API的地址填入到IAT里,而是将节表5的地址,从起始位置开始,每隔16个字节,将地址填入到IAT里,然后在对应的节表5地址上填入push 真实函数地址 + retn的汇编指令。这样一来,原PE程序运行调用API时,就会跳到节表5里面,再从节表5里面跳到真实API地址,直接干掉了x64dbg的脱壳导入表自动修复功能。
虚拟壳(技术要求过高 以后写 “如果虚拟壳写的好直接拿去卖也不用工作 年入50w+”)
虚拟技术应用到壳的领域,设计了一套虚拟机引擎,将原始的汇编代码转译成虚拟机指令,要理解原始的汇编代码,就必须对其虚拟机引擎进行研究,而这极大地增加了破解和逆向的难度及成本。
随机花指令构造器
构造花指令,可以使用无条件跳转,比如ret、call、jmp来跳转,也可以使用有条件跳转,跳转的越多,能给人造成的困扰越大。
兼容
xp-win11 x64 x32 都得兼容
内核型加壳器 .sys的
伪装OEP
压缩资源
注意 是压缩资源 不是压缩区段
换句话说就是加壳压缩
用到的有:
哈夫曼编码(好像已启用) UPX源码 IEXPRESS UPX ASProtect WinRAR NSPack DarkCrypt
alib原理
1:当压缩算法扫描到中间某段位置时,如何和前面的内容进行快速比较。
1 |
|
2:如何区分当前是压缩的内容,还是未压缩的内容。
1 |
|
哈夫曼编码
左节点右节点相加提供给单链
1 |
|
lzma (lz系列)
采用马尔科夫链主要利用马尔科夫随机过程来消除原始文件中的基于上下文的冗余(如英文中字母Q后面紧接的字母为U的概率远较其它字母大),而不仅仅是哈夫曼编码中单纯的基于字符出现的随机统计概率
就是abcdefgabc转换成abcdefg73
quicklz 号称世界上最快的压缩算法
1 |
|
在压缩的过程中不断地读入3个字节,然后根据这3个字节得到一个hash值,根据这个hash值就可以找到offset,这个offset就是上次这个hash值出现的位置,而通过cache可以判断出这次出现的和最近一次出现相同hash值的时候的3个字节是不是相同(可能hash相同而实际的值不同)。
level1 四个字节四个字节的hash对比 获取重复值
level2 对比4个offset的hash 选取其中最长的当模板
level3
1 |
|
在level=3的时候与前两个的不同之处是最低的两位统一作为标志
http://www.wjhsh.net/xumaojun-p-8541618.html
图形化
壳代码纯shellcode开发
将壳代码编译为Shellcode代码,方便移植、混淆、特征码定位。Shellcode开发方法论坛搜索即可,非常多的帖子。
API字符串隐藏
Shellcode编程的常规编写技巧,将API字符串转为HASH,壳代码通过HASH来获取API地址。此技术主要用于缩短Shellcode体积、干扰分析人员分析。
Shellcode动态获取外部参数
参考”图-CodeLoader数据结构”可以发现壳代码CodeLoaderCode(EntryPoint函数生成在壳代码CodeLoaderCode的首部)是储存在PARAM_CODE_LOADER结构体的尾部。
因此只需要动态定位到&EntryPoint函数的内存地址,然后减去sizeof(PARAM_CODE_LOADER)就可以获取到加壳器传递的参数数据了。
简单图片隐写技术(最大化的压缩体积)
入口点模糊技术
QVM引擎已经对主流编译器编译的程序,从入口点==(OEP)==开始提取==一段==特征代码作为判断依据,因此只要修改入口点代码QVM引擎就会报“HEUR/Malware.QVM20.Gen”。
杀软检测入口点代码的绕过方法:
1、 使用壳代码完全伪造主流编译器编译的程序的入口点特征
2、 不修改入口点开始处的代码,而是在QVM引擎提取的入口点特征代码的==尾部劫持执行流==(此壳就是使用的这个方法)
内存加载PE&支持壳上壳
当前使用的内存中加载PE技术,是通过将”被加壳程序”内存展开后,覆盖到当前进程的ImageBase处,随后修复”被加壳程序”的IAT表,设置区段属性,最终调用”被加壳程序”的OEP将执行权限交给”被加壳程序”。这种写壳方式的天生就支持壳上壳功能。
壳上壳的功能主要是为了躲避内存查杀。可使用VMP、TMD、SE等虚拟化壳的代码虚拟化功能来模糊化被加壳程序的内存特征,当然使用自写的VM、混淆引擎更好,==不过写一个稳定、兼容性好的VM、混淆引擎耗时太长。==
加多重壳需要注意关闭内层壳的校验基址,如下图的VMP:
兼容性较好的修复IAT表方法
使用双向链表
兼容性较好的修复导入表(IAT)方法,优先使用INT表来获取API地址。解决有些编译器编译的程序导入表(IAT)不规范的问题(Delphi)。
1 |
|
长时间执行垃圾指令进行沙箱逃逸
反注射
- 使用EnumProcessModulesEx (32位、64位和所有选项)枚举模块
- 使用工具帮助枚举模块32
- 使用LdrEnumerateLoadedModules枚举流程LDR结构
- 直接列举流程LDR结构
- 具有GetModuleInformation的行走记忆
- 隐藏模块的行走记忆
反dumping
- 从内存中擦除PE头
- SizeOfImage
定时攻击[反沙盒]
https://github.com/LordNoteworthy/al-khaser#antidebug
人类互动/通用[反沙盒]
反虚拟化/全系统仿真
1 |
|
Anti-Disassembly
- 以恒定条件跳跃
- 目标相同的跳转指令
- 不可能拆卸
- 函数指针
- 返回指针滥用
其它操作
如果在外壳程序中,有复杂的操作,要用到容器,比如矢量、链表或者树,不能用Windows提供的标准模板库,因为这里面说不一定就会用到API,从而导致程序出错。
可以仿照标准模板库设计自己的库,比如设计一个vector容器
https://www.52pojie.cn/thread-1277269-1-1.html
在外壳里面怎么获取随机数的问题,可以直接逆向(直接改写源码)srand()和rand()两个函数,
以下的随机数函数就是照搬srand()和rand()
垃圾指令构造器的设计非常简单,难点在于垃圾指令的选择,有些指令是不能作为垃圾指令的,改变普通寄存器的指令不能用,比如AAA指令,会改变eax寄存器的值。具体参考Intel手册。
但是=======================================================
其实也不是不能用,可以对上下文分析看哪些寄存器暂时未被使用,然后对其赋值。如果有 CALL/JMP 可以暂时先跳过。
而且感觉插入 CALL/JMP 后很容易破坏自动分析工具?可以在垃圾代码中插入一堆看上去有用实际上没用流程控制/空函数调用。
1 |
|
也可以插一段算法,比如趁着某个寄存器还未被使用的时候修改,然后用算法还原到之前的状态:
1 |
|
免杀方法
当前主流的技术,需要有源代码才能操作。通过修改病毒的特征字符串、动态API调用、修改编译环境、套程序外壳(MFC、SDK、QT)等
输入表(IAT)免杀法
发式引擎会扫描目标程序的输入表中是否包含指定的函数特征序列(函数调用特征码)。
解决方案:(本段摘自未知作者)
1、 输入表函数移位法:这是最早也是比较简单的输人表免杀方法了,虽然效果已经不像当年那么好了,但却是学习免杀过程必须要掌握的基础知识。我们使用C32打开一个EXE文件,找到输入表段,找到我们定位出的特征输入表,比如ShellExecuteA就是,我们将其使用OO填充,然后在附近找到一片空白区域,将刚才找到的代码再粘贴到空白区域中,并记下新函数的地址,ShellExecuteA字符串最前面的那个“S”的地址减2,即00078925,这样就实现了转移,但是要想让程序知道我们转移的函数,我们还得告诉输入表,刚才那个地址是文件偏移地址,但不是内存地址,我们需要利用OC计算出新的输入表函数ShellExecuteA的内存位置,并在LoadPE中修改才行。
2、 输入表函数对调法:这个方法的原理就是将输入表函数名长度相同的函数在C32中进行对调,只有长度一样才不会出错,然后在LoadPE中做相应的修改即可。比如被查杀的函数是OpenFileA,存在于A.dll文件中,我们在b.dll中找到了一个GetATimeA函数,这两个函数名称长度一样,我们在C32中做了静态对换之后,还要将它们的RVA进行对换
3、 手工重建输入表:关于输入表的重建,我想大家都非常熟悉了吧,这算是比较复杂的一种方法了,不过免杀效果非常好,这也是必须要掌握的方法哦!这个方法其实就是添加一个新区段,再把原来的输入表移到我们新建的区段上,重建主要是针对杀毒软件定位到大片输入表函数。
4、 输入表隐藏法:将输入表加密隐藏,然后内存解密修复输入表。属于保护壳常用的技术。
代码混淆、加花:通过对特征代码进行膨胀、乱序来干扰启发式引擎的分析,以及提升人工提取特征码的难度。
入口模糊技术
内存加载执行PE文件:壳的基本技术,论坛资料很多。
机器学习引擎(以360QVM为例),绕过方式如下:
模拟正常程序的PE结构(该免杀壳方案能有效针对该引擎,或许还能污染机器学习引擎的分析结果)
常见QVM引擎报毒原因:
HEUR/Malware.QVM06.Gen 一般情况下加数字签名可过HEUR/Malware.QVM07.Gen 一般情况下换资源HEUR/Malware.QVM13.Gen 加壳了HEUR/Malware.QVM19.Gen 杀壳HEUR/Malware.QVM20.Gen 改变了入口点HEUR/Malware.QVM27.Gen 输入表HEUR/Malware.QVM18.Gen 加花HEUR/Malware.QVM05.Gen 加资源,改入口点
沙箱(虚拟机)行为分析引擎,绕过方式如下:
简介:所谓“沙箱”安全技术,是指以计算机系统为基础对恶意软件的行为与特征进行分析并最终检测出恶意代码的方案。
解决方案:
1、通过检测沙箱(虚拟机)与物理机的差异化(参考:https://bbs.pediy.com/thread-225735.htm),检测到沙箱(虚拟机)则不执行恶意代码。
2、延时180+秒(效果比较好)加载恶意代码,沙箱(虚拟机)的检测结束后无法探测到恶意行为。笔者比较推崇此方法,因为此方法针对的是所有反病毒厂商的沙箱(虚拟机)检测。
3、挖掘开机启动程序的代码执行漏洞,配合白加黑技术来执行敏感行为。
主动防御:
简介:主动防御是基于程序行为自主分析判断的实时防护技术,不以病毒的特征码作为判断病毒的依据,而是从最原始的病毒定义出发,直接将程序的行为作为判断病毒的依据。
360主动防御模块经过做黑灰兄弟们的不懈努力,已经非常完善了,绝大部分常规、非常规的行为绕过方式均已被拦截,并弹出一个默认阻止的小框框。
1、继续挖掘非常规方法绕过主防的拦截,主防未监控到的区域。
2、白程序(包含在杀软白名单库中的程序)加黑程序方式来执行高危行为,写启动项、键盘记录等。(不过要注意的是360白程序判定逻辑,灰程序加载的白程序 = 灰程序,因此需要绕过主防的程序执行链监控)
3 免杀壳开发(by: AYZRxx)
3.1 免杀壳核心思想-伪装
经过10多年的发展,反病毒引擎已经在误报&查毒粒度之间取了一个比较好的平衡,常规的免杀技术(特征码免杀、源码免杀)处理成本越来越高。不过反病毒引擎天然存在某些”缺陷”,例如正常软件会加商业保护壳,导致会受到商业壳的制约,无法将所有壳标记为病毒。
由于内存执行”被加壳程序”是壳的基础行为,==而内存执行PE这个”壳的基础行为”可以很好的将”被加壳程序”的特征码隐藏起来。==因此编写一款无特征码壳是一个非常好的反杀软查杀(特征码、启发式)的方案。
\1. ==模拟正常PE程序结构, 模拟正常PE程序结构, 模拟正常PE程序结构==
\2. 特征代码最小化,并且==被查杀后==可通过==混淆引擎==来混淆壳代码,达到快速变种、快速免杀的效果。
\3. 笔者不建议进行任何可能提高程序熵值的操作,==尽可能将壳程序的PE格式、数据结构、代码执行顺序与正常程序保持一致。==
3.2 免杀壳的编写框架说明
这个免杀壳的代码主要分为三部分:
加壳器:这部分代码用来将被加壳程序、傀儡程序、壳代码拼装处理,组合生成一个免杀的PE文件。
CodeLoader(壳代码):这部分代码用来反杀毒引擎、内存加载执行PeLoader,需要编译为Shellcode代码。
PeLoader(壳代码):这部分代码用来内存执行Shelled(被加壳程序),需要编译为Shellcode代码。
1 |
|
1 |
|
先看看别人的代码分析分析
Peprotect
stub
stub.cpp(植入的.cpp)
1 |
|
dllmain.cpp
1 |
|
peprotect
peprotect.cpp
peprotectdl.cpp
用来设置对话框
pack
dllmain.cpp
1 |
|
PE.cpp
1 |
|
Pack.cpp
1 |
|
Packer-master
stub.dll
aplib.lib– 壳压缩引擎
info.h – c++文件系统 管理写读
dllmain.cpp (直接将创建线程和功能都写在里面了)(用到了内存管理)
1 |
|
packer.exe
InputInfo.cpp : 实现文件
1 |
|
PackerDlg.cpp :对话框
1 |
|
pictureEx.cpp :界面装载图片
util.cpp
1 |
|
Task.cpp
1 |
|
loading.cpp
1 |
|
内存管理是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。
GuiShou_Pack-master
这个是分阶段写的 可能有助于理解
阶段1–基础功能实现
main.cpp —大部分函数调用的CPeFileoper.cpp
1 |
|
CPeFileoper.cpp
1 |
|
stub.cpp
1 |
|
阶段2–增加弹框
stub.cpp
1 |
|
main.cpp 不需要改
CPeFileoper.cpp 不需要改
3.0 增加反调试
stub.cpp
1 |
|
main.cpp
1 |
|
4.0 增加AES加密
stub文件夹多了个AES.cpp
stub.cpp
1 |
|
stub.h
1 |
|
CPeFileOper.cpp
1 |
|
5.0 增加Tls回调函数的调用
1 |
|
6.0 增加花指令
stub.dll
1 |
|
这段花指令和壳程序我在各大代码看了不下7遍:joy:
7.0 增加全部区段加密
stub.dll
1 |
|
CPeFileOper.cpp
1 |
|
main.cpp
1 |
|
8.0 增加IAT加密 加花
stub.cpp
1 |
|
CPeFileOper.cpp
1 |
|
8.0 增加IAT加密 未加花
对比加花 stub.cpp
加花 | 未加花 | |
---|---|---|
在壳程序函数里 | //获取函数的API地址 FusedFunc((DWORD)GetApis); //解密代码段 FusedFunc((DWORD)Decrypt); //恢复数据目录表 FusedFunc((DWORD)RecoverDataDir); //修复IAT FusedFunc((DWORD)FixImportTable_Normal); //反调试 FusedFunc((DWORD)AntiDebug); //密码弹框 FusedFunc((DWORD)AlertPasswordBox); //调用Tls回调函数 FusedFunc((DWORD)CallTls); //加密IAT FusedFunc((DWORD)EncodeIAT); | 不执行加壳函数 |
在start函数后 | 执行壳 FusedFunc((DWORD)AllFunc); | |
//获取函数的API地址 GetApis(); //解密代码段 Decrypt(); //恢复数据目录表 RecoverDataDir(); //修复IAT FixImportTable_Normal(); //反调试 AntiDebug(); //密码弹框 AlertPasswordBox(); //调用Tls回调函数 CallTls(); //加密IAT EncodeIAT(); | ||
总 | 所有函数都调用了FusedFunc函数 | |
FusedFunc函数 | 加花函数 需要自己再改改 |
9.完整体
stub.dll
1 |
|
AtomPepacker
ArgsTest
判断args输入命令
hasher
应该是使用hash加密(散列替换)api函数字符串
DLLPP64Stub
General.c
就是自定义一些字符转换
Utils.c
InitializeDirectNtCalls 初始化调用nt结构
GetDllFromKnownDlls 调用kernel.dll获取各类dll
hSection = NtOpenSection 获取NT节句柄
RefreshNtdll 需要时重新分配ntdll
GetModuleHandleH(自定义api哈希库) 从peb的ldr的pdte的InMemoryOrderModuleList.Flink 中枚举全部加载模块 然后获取模块句柄
LoadLibraryH(自定义api哈希库) 将指定的模块加载到调用进程的地址空间中。指定的模块可能会导致其他模块被加载。对于其他加载选项
无CRT导入
RtlInitUnicodeString 初始化设备名称指针。
IoDeleteSymbolicLink 例程从系统中删除符号链接
IoDeleteDevice 删除驱动
Dbgprint extension 显示以前发送到dbgprint缓冲区的字符串。
Shell_Protect-main(虚拟壳)
这个就有点难了 之后分析 别一口吃成胖子 先自己写个简单的加壳 然后改进 然后写这个虚拟化壳
就连压缩的代码都不一样 虚拟化实在太强了
compressiondata
1 |
|
反汇编引擎
用的是capstone
capstone 可以说是所有反汇编引擎中集大成者,对于它我要多费点口水,因为我对他是又爱又恨。capstone是基于LLVM框架中的MC组件部分移植过来,所以LLVM支持的CPU构架,capstone也都支持。
它支持的CPU构架有:
Arm, Arm64 (Armv8), M68K, Mips, PowerPC, Sparc, SystemZ, XCore & X86 (include X86_64)
而且Capstone对X86构架的指令集支持是最全的,这一点是其他引擎都比不上的,其支持的X86扩展指令集有:
3dnow, 3dnowa, x86_64, adx, aes, atom, avx, avx2, avx512cd, avx512er, avx512f, avx512pf, bmi, bmi2, fma, fma4, fsgsbase, lzcnt, mmx, sha, slm, sse, sse2, sse3, sse4.1, sse4.2, sse4a, ssse3, tbm, xop.
原来这东西是提供给虚拟机使用的
反汇编stub文件->每条汇编挂钩handler->当eip执行地址则进入虚拟机处理
然后compresstiondata会调用跟这个vm进行vmentry
这个就用来compressiondata前进行vm的解密
之后我们分析这些源码有哪些是相通的函数 然后来看一下函数有没有细致差别
Peprotect和guishou_pack-master的sutb.dll代码差不多
那我们进行pack-master和guishou_pack-master的stub.dll的相似函数的内容分析
pack-master | guishou_pack-master | |
---|---|---|
#头 | #include “aplib.h”压缩引擎 | |
info.h | ||
AES.h | ||
define IMAGE_SIZEOF_BASE_RELOCATION 定义基址重定位表 | ||
define LOG_PATH L”D:/log.txt” 打印日志设置 | ||
define ANTI_REVERSE 反调试设置 | ||
define SUPPORT_LOG 打开默认日志 | ||
中 | 定义一堆内存数据流结构体和自定义的内容管理函数 | |
开始 | getNtHeader和getImageSectionHeader | 获取各种头 |
又写了两个内容管理函数 | ||
isdebug反调试 | ||
又定义一些七了八了的内容管理函数 | ||
根据pUnhandledExceptionFilter和pUnhandledExceptionFilter1使用汇编编写debug1 | ||
TlsCallBack | ||
汇编获取kernel | better (kernel+GetGPAFunAddr)的汇编版hKernel32; | |
GetGPAFunAddr | DOS头\Nt头->导出表项->导出表详细信息->根据函数名查找详细地址值 | |
利用GetGPAFunAddr获取API地址 | 利用hKernel32获取地址 | |
编写getExePath获取进程地址 | ||
利用getExePath实现那些文件的自删除 不知道有个鬼用 | ||
总结 | 除了多了解压缩函数和内存管理啥也没有 | 使用汇编定位api使代码更好写 而且还做了加密 加壳 花指令等函数 |
上面这个的话我感觉还是用汇编获取api更好用一点
stub.dll里面的函数InitTls和callTLS的区别
InitTls(pack-master) | callTLS(guishou_pack-master) | |
---|---|---|
获取pTlsCallBack和pStubCallBack的虚拟地址 | (DWORD)pfnGetMoudleHandleA(NULL);获取程序的句柄加载基址 | |
直接用虚拟地址获取TLScalbacke | GetOptionHeader((char*)dwBase)->DataDirectory[9].VirtualAddress;获取内存地址 | |
pTlsTab = (PIMAGE_TLS_DIRECTORY)(dwTlsRva + dwBase);TLs表的句柄加载基址和内存地址 | ||
nTlsCallBacks = (DWORD)pTlsTab->AddressOfCallBacks;如果获取TLS表成功 引用AddressOfCallBacks; | ||
使用汇编call nTlsCallBacks |
修复IAT(都是用的动态修复 因为计算机每次重启.系统dll映射到exe程序所在的地址都会变,IAT必须动态修复).修复IAT表需要用到”LoadLibraryA”和GetProcAddress两个函数,这两个函数存在于KERNEL32这个DLL中,可以通过FS寄存器找到进程环境块PEB,得到kernel32的地址,找到GetProcAddress函数所在地址,再通过GetProcAddress找到LoadLibraryA的地址。
recoverIAT | FixImportTable_Normal |
---|---|
//获取当前程序的加载基址<br/HMODULE hModule = s_apier.GetModuleHandleW(NULL); 然后做出了判断是否获取成功 | //设置文件属性为可写 SetFileHeaderProtect(true); |
获取NT头进而获取加载基址 | //获取当前程序的加载基址 DWORD ImageBase = (DWORD)pfnGetMoudleHandleA(NULL); |
//导入表 lpImportTable = (IMAGE_IMPORT_DESCRIPTOR*)((DWORD)lpImageBase + g_globalVar.dwIATVirtualAddress); 这里的dwIATVirtualAddress 是自己定义的结构体 | //导入表=导入表偏移+加载基址 IMAGE_IMPORT_DESCRIPTOR* pImp = (IMAGE_IMPORT_DESCRIPTOR*)(GetOptionHeader((char*)ImageBase)->DataDirectory[1].VirtualAddress + ImageBase); |
获取IAT,这是需要将函数地址写入的地方,但是相比右边缺少了校验,默认为获取PINT | 根据导入表获取Int 如果没有INT就获取IAT |
后面跟右边差不多 我感觉右边的代码更好写且好用一点 | // 加载dll hImpModule = (HMODULE)pLoadLibraryA((char*)(pImp->Name + ImageBase)); |
while导入函数地址u1.Function if (IMAGE_SNAP_BY_ORDINAL)判断导入的方式、序号还是名称IMAGE_SNAP_BY_ORDINAL(pInt->u1.Ordinal 来获取impaddress | |
pVirtualProtect保护piat的导入函数地址u1.Function | |
将刚才获取到的pint的impaddress赋值给pIat->u1.Function | |
修复原始重定位表 (只有pepacker里面有)
1 |
|
pepacker也多了decompress解压缩函数
修复重定向估计也是为了配合解压使用的
那我们直接改guisuo_packer的代码 compress代码
1 |
|
开始写
写个压缩区段 先分析一下流程
1 |
|
再看一下encrypt加密区段函数
1 |
|
为了更好的编写和使用 得写一个函数用于获取rsrc和tls段的信息
好麻烦啊 直接在peprotect写加密吧
stub
解密代码段
加密iat
反调试 x3个 (后面可以嵌套ollvm)
解压和压缩区段
密码弹框
调用Tls回调函数 (用于反调试)
修复exe重定位
恢复数据目录表
混淆函数
修复iat
加花函数
先看一下什么时候需要修复iat
修复iat 换句话来说就是修复导入表 importtable 但是都常见于脱壳
因为原来的程序在获取完IAT后就把字符删掉了,文件偏移里的字符串指针没有了,就没法自动获取地址了。所以必须脱壳之后必须把IAT修复好
那么解密代码段话就是类似一个脱壳的过程 可以会导致rva的偏移,这就需要我门修复iat去重新获取。
那么我们也要对应的去改一下pack.cpp
接下来看下要不要对那个虚拟壳进行改写学习
抹去PE指纹(不过这类常规方法只能抹去内存中的pe头)
1 |
|
有没有写入文件的方法
都不能运行:angry: 改addshelltool
接下来改高级的
- 兼容x64
error1
__declspec(naked) naked 无法识别的扩展特性
__declspec(naked) 用来将汇编语言嵌入到c语言中,手工处理堆栈,即使是裸函数 我们也能运行
原来是这样
原因是x64不支持使用内联汇编代码
1.一个方法是直接把汇编代码转换成.asm文件然后再去引入
2.参考vmpshell的代码
做了判断函数
#ifdef _WIN64
直接 __stdcall之前的kernel 和分配好的函数
else //win32
重新定义获取puGetModule和MyGetProcAddress函数
//都是用来给下面sheller_code调用
我们就用第二套办法先试试
我们的目的都是为了获取windowsapi函数 但是在win64的话不需要先获取基址
而是可以直接 void _stdcall调用,所以我们只要知道我们需要哪些函数然后去定义就行
由于这个pGetProcAddress 我们自己定义的 x64下也没问题 所以我们直接用x64获取kernelbase就可
写到一半 发现好麻烦 除非之前已经定义过api了
我们用第一种方法
参考这个
https://blog.csdn.net/Giser_D/article/details/90670974
在asm定义好了函数 所以在只要写好了头文件和cpp 就不再需要声明
怪不得 vmshell的x64代码频繁用到 __stdcall
都是从asm里面调用的(但是是在cpp里面调用)
所以根本就没有第二种方法
所以我们需要改的就需要这三个东西
1 |
|
也就是说这个_stdcall写在cpp里面就可
只要stud.h引用了STUD_H 之后cpp调用stud.h就可
与kernelbase的也只有这一段 后面的都可以用pGetProcAddress和pLoadLibraryExA获取
这一段的是getkernelbase-> pgetprocaddress -> 获取各类函数
而这些函数我们都可以用汇编直接获取
- pLoadLibraryExA typedef定义 调用getprocaddress获取 汇编没有
- pGetProcAddress 汇编有 但是我们代码也自己定义了
- pExitProcess typedef定义 调用getprocaddress获取 汇编没有
- pVirtualProtect typedef定义 调用getprocaddress获取 汇编没有
- pGetLastError typedef定义 调用getprocaddress获取 汇编没有
- pVirtualAlloc typedef定义 调用getprocaddress获取 汇编没有
- pVirtualFree typedef定义 调用getprocaddress获取 汇编没有
- pVirtualQuery typedef定义 调用getprocaddress获取 汇编没有
汇编有的
1 |
|
只能直接获取LoadLibrary函数 看下怎么用的
1 |
|
找了半天找不到直接asm获取kernelbase的办法 可能需要重构直接获取api了
判断PE 64 DLL NET
1 |
|