动手编写远控工具及检测思路

动手编写远控工具及检测思路
tammypi FreeBuf 1周前
PS:本文仅用于技术讨论与交流,严禁用于任何非法用途,违者后果自负

在学习攻击渗透的过程中,不免会接触远控工具。远控工具一般包含服务端和客户端,服务端运行在攻击者的VPS主机上,客户端运行在被攻击机器上。服务端向客户端发送指令,客户端执行指令并将结果回传给服务端,从而达到通过网络远程控制被攻击主机的效果。

现有的远控工具很多,从大名鼎鼎的冰河到CHAOS。但是直接使用现有的远控工具,一方面会担心工具被人加入了后门在运行的过程中自己反而成了被控制方,另一方面只会使用工具也会沦为“脚本小子”而不知道其背后的原理。

本文详细介绍了一款最小功能集的远控工具的实现细节,按照步骤动手实现,不仅可以对于远控工具的背后思路有了更深的体会,也可以巩固自己在多线程、网络编程等方面的知识。

一、总体结构


图1.总体结构

同一般的远控工具一致,我们要实现的这款工具也是包含服务端和客户端。

服务端运行在VPS主机上,监听443端口。由于443为HTTPS协议端口,这样被攻击主机上即使监控到存在外联443端口的流量,也不太容易引起重视。所以在模拟攻击者的过程中,可以选用一些常用端口作为服务端的监听端口。

服务端和客户端使用TCP协议进行通信,利用多线程服务端处理多个客户端同时在线的情况。

客户端捕获异常,当与服务端的通信连接断开触发异常时,延时5秒后重新发送连接请求。这样即使服务端出现了中途退出的情况,重新运行后客户端也可以重新上线。

二、实现细节
2.1 服务端
服务端主要做两件事情:

使用socket监听443端口,使用独立的线程完成与客户端的通信,对于指定客户端下发指令并打印指令执行结果

在主线程中死循环监听攻击指令输入(如snapshot对于被攻击机进行截屏、cmd在被攻击机上执行系统命令)

图片

图2.服务端记录客户端连接流程

图2即是服务端记录客户端连接的流程。客户端发来的第一条消息会是“HELO,客户端IP”,服务端会解析第一条消息,并以客户端IP为key将客户端的socket保存到字典里。后续,当需要与指定的客户端进行通信时,直接从字典里根据IP拿到对应客户端的socket进行消息的发送和接收即可。

图片

图3.服务端处理指令流程

图3则是服务端处理指令的流程。目前服务端接收的指令如下:

ls: 打印当前全部客户端IP

snapshot:对于指定客户端所在机器进行截屏。示例:snapshot ${client_ip}

clipboard:获取客户端所在机器的剪贴板内容。示例:clipboard ${client_ip}

cmd:在指定客户端所在机器执行系统命令。示例: cmd ${client_ip} ${command}

showdriver:展示客户端所在机器的全部盘符。示例:showdriver ${client_ip}

getfilelist:获取客户端所在机器指定路径下的文件列表。示例:getfilelist ${client_ip} ${path}

getfile:获取客户端所在机器指定文件。示例:getfile ${client_ip} ${filepath}

当服务端接收ls指令时,直接将记录有客户端socket字典的key列表打印出来即可。当收到的是其他指令时,由于指令的第二个参数是客户端IP,那么根据IP得到对应的客户端socket,使用socket将指令发送给客户端,并打印客户端socket的返回结果即可。

2.2 客户端
客户端主动连接服务端的443端口。在初次连接上时,主动发送一条“HELO,客户端IP”消息标记自己。后续客户端则是持续循环等待服务端发送过来的指令,并根据指令真实的在被攻击上进行截图、获取文件列表等操作,并将结果回传给服务端。

客户端会捕获异常,当socket出现异常后,等待5秒尝试重连。

图片

图4.客户端处理指令流程

三、重点问题解决
3.1持久化
一个远控工具除了支持服务端下发指令,客户端执行指令并回传结果的功能外,还需要可以对于自己进行持久化。所谓持久化,即保证客户端进程被杀死或者被攻击主机关机后,也可以在被攻击主机再次开机时启动。

进行持久化有如下几种方法:

写注册表

将执行文件复制到启动目录

将自己注入其他系统进程

这几种方法各有优缺点,将执行文件复制到启动目录一般需要管理员权限,在执行客户端进程的用户没有管理员权限时,复制会导致“Permission denied”错误。而写注册表和注入进程的行为,通常会被杀软作为高危项检测到。

由于我们的这款远控工具只是学习使用,所以不考虑绕过杀软的方法,使用写注册表的方式将自己设置为开机自启动:

图片

图5.写注册表开机自启动

主要的步骤就是首先将自己复制到临时目录,并且改名为svhost.exe(与系统进程svchost.exe一字之差),同时将自己写入注册表的开机自启动项。

3.2 TCP Socket通信粘包问题
由于我们实现服务端和客户端的通信是用的TCP协议,而TCP协议容易出现粘包问题,导致服务端接收到的客户端回传的图片或者文件是损坏状态,无法正确打开。

所谓粘包即客户端发送给服务端的多个包被粘在了一起,它产生的主要原因在于接收到数据包放在了缓冲区里,如果缓冲区包写入的速度大于服务端从缓冲区取的速度,服务端就会取到首尾相连的多个包。

为了让服务端可以正确的区分接收到的数据,解决粘包问题,我们需要将传输的数据进行格式化。

我们定义:

一条消息的头部4个字节为消息内容长度

头部后面再跟消息内容

图片

图6.发送格式化的消息

当服务端接收到数据时,先解析头部4个字节得到消息的长度,再根据这个长度得到消息正文。

图片

图7.处理格式化的消息

3.3 客户端打包为exe
我们的这款远控工具使用的python语言进行开发,而客户端主要运行在windows主机上。由于windows主机默认并没有安装python环境,所以需要将客户端打包为exe使得它可以直接在windows主机上进行运行。

将python打包为exe主要使用pyInstaller这款神器,使用-F参数生成单个执行文件,使用-w参数表示运行时去掉控制台窗口。打包完成后,dist目录下即会出现生成的exe文件。

图片

图8.将客户端打包成exe

四、效果演示
服务端运行时,当客户端上线时,会打印客户端的IP。

图片

图9.服务端运行效果

使用ls命令,可以打印当前已经上线的全部客户端IP:

图片

图10.打印全部客户端

可以对于客户端所在机器进行截屏:

图片

图11.对于客户端所在机器进行截屏

五、源码下载地址
https://github.com/tammypi/remote_control_tool

强调:请注意,本源码仅供学习使用,请勿用作非法用途。

六、检测思路
在对于这类远控工具进行检测时,重要的特征有以下两点:

1.注册表写入动作、复制自身动作
2.虽然与常用端口进行通信,但是数据包的特征却明显并非为对应协议的特征
图片

恶意代码分析入门系列之 - 基础知识
 
 
在第一小节完成了环境的搭建,成功搭建好了一个可用于恶意样本分析的虚拟机环境,在这小节,先介绍一些关于恶意样本的相关知识,也算是为恶意样本分析做准备。
 
首先,我没有写详细的汇编教程,论坛中有非常多的汇编教程,读者可以多多利用论坛的搜索功能,找到一些相关的知识点。大多数情况来说,遇到的问题之前都有人在论坛中求问过,只是措辞和询问的方式不一样,大家遇到问题的时候可以先利用论坛的搜索功能或结合google进行搜索。
 
为了不让之后的内容过于抽象,在本小节中介绍基础的汇编知识。
 
 
0x00 汇编基础内容
 
寄存器相关
    
简单来说,寄存器是CPU内部用于存放数据的小型存储区域,寄存器是内存与CPU能够交互的一个关键。
    
以x86为例,常见的有8个通用寄存器。 
  • EAX    eax是累加器,在做加法和乘法运算的时候,eax是默认寄存器    
  • EBX    ebx是基地址寄存器,通常被用于寻址    
  • EXC    ecx是计数器,通常用于循环计数    
  • EDX    edx通常用于存放除法运算产生的余数    
  • ESI     esi是源寄存器,通常在字符串操作中使用
  • EDI    edi与esi对应,edi表示目标寄存器    
  • EBP    ebp是基址指针,通常用于指向栈底    
  • ESP    esp是堆栈指针,与ebp对应,通常指向栈顶。
    
还有一些特殊的寄存器,刚开始的时候不用掌握那么多,后面遇到再去学习就行。
 
eax ebx exc edx 这四个寄存器,除了本身自带的一些功能,他们还常常被用于一些运算。开发人员可以直接使用一些汇编指令去操作这几个寄存器实现一些简单的功能,需要注意的是,eax寄存器还有一个存放函数返回值的功能。在大多数情况下,一个函数调用完成之后,返回值都会保存在EAX寄存器中。
 
后面的esi和edi通常会配合数据段寄存器DS来使用,通常情况下,我们不会动esi和edi的值。
 
esp和ebp分别用于指向栈顶和栈底,是两个特别关键的寄存器。esp和ebp涉及到操作系统中一个最基础的概念"堆栈"。
 
说是堆栈,其实是指栈,当我们描述堆的时候都会直接说堆。
    
栈的原理其实很简单,从C语言的角度来看,栈是一种数据结构,先入后出。从计算机系统来看,栈是一个具有先入后出属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。可以说我们所见到的函数调用,基本上都是跟栈挂钩的。
    
在一个函数开头,经常可以看到如下的语句来开辟新的堆栈:
push ebp mov ebp,esp esp 40h
 
这里首先保存当前的ebp,然后通过mov将ebp和esp指向同一个位置,再通过sub指令更改esp的指向,这样就为当前函数开辟出了一个新的栈空间。
 
sub esp 40h里面的40h,是编译器预留给当前函数的空间大小。
 
然后在函数结尾,通常会使用:
mov esp,ebp pop ebp ret
 
作为返回,退出当前函数。
 
在汇编代码中,ebp常见的有两种使用方式,分别是:
ebp + xx ebp - xx
    
根据windows内存空间的生长方向我们可以知道,ebp + xx 是取参数的值,ebp -xx 是取局部变量的值。
    
有时候也会使用esp的加减来定位变量,但是这种情况比较少见。
 
举个例子:
   
我们在对一个函数调用的时候,就会将一些关键信息入栈。
    
最开始入栈的是函数的参数,在C语言中,函数的参数入栈顺序是自右向左依次入栈。
    
最后入栈的是函数的返回地址,这个地址很重要,关系着函数执行完成之后该回到哪里继续执行。
 
比如我有个函数fun1(int a, char b, int c ),我在main函数中调用了fun1函数,那么参数入栈的时候,最先入栈的是变量c,其次入栈的是变量b,接着入栈的是变量a,最后入栈的是main函数调用这个fun1函数之后的地址。
 
汇编指令相关
    
先介绍几个常见的指令:mov、add、sub、push、pop、call、rete
 
最常见的汇编指令是mov,mov指令有两个操作数,格式如下:
mov 操作数1,操作数2
 
mov指令的功能是将操作数2的内容移动到操作数1,这里的操作数1必须是寄存器,操作数2可以是地址、常量、寄存器。例如:
    
mov eax,31 是把0x31赋值给eaxmov eax,ebx 是把ebx的值赋值给eax。
    
那么可不可以用 mov 0x00401000,31 来表示将0x31放入到0x00401000这个地址呢,答案是不行的。
    
因为如果第一个操作数变成了地址,编译器并不能保证这个地址一定存在,而数据是从内存中读入到CPU中的,要让CPU能够正常操作,所以其中一个操作数必须为寄存器。
 
add和sub是一组运算指令,分别对应加减运算。这两个指令和mov类似,使用方法都是:
    
add  操作数1,操作数2sub  操作数1,操作数2功能是将操作数1和操作数2进行add或者sub操作,然后结果保存在操作数1的位置所以很明显,操作数1也必须是一个寄存器。比如
    
add eax,0x10是将eax中的值和0x10相加,然后结果保存在eax中add eax,ebx 是将eax中的值和exb相加然后保存在eax中。sub指令也是同理。比如我要利用eax进行运算可以这样写:
mov eax,0x10 add eax,0xA mov ebx,0x05 add eax,ebx
 
还有一组常用的汇编指令是push和pop,和之前的汇编指令不同,push和pop只有一个操作数。
比如:
    push eax
    push ebx
    push 0x10
    pop edi
    pop ebx
    pop bex
    ......
 
push指令的功能是将跟在push指令后的操作数入栈。
    
当一条push指令执行之后,ebp的值不变,esp的值会减去入栈类型的长度。
    
比如假设当前的栈如下:    
栈底ebp指向的地址是0x0012FF80
栈顶esp指向的地址是0x0012FF70
现在执行push 0x10,那么堆栈的变化如下:
与之对应的,pop指令执行之后,ebp还是不变,esp的值加上pop出的类型的长度。
 
关于汇编的这部分,笔者去年写了一点点在公众号上,有兴趣的读者可以看看https://mp.weixin.qq.com/s/8ZdX7cOLp18FMtE5vBgTkA。
 
由于图片转过来不清晰,又有水印,这里就不继续讲汇编了,讲点后面的内容。
 
 
0x01 恶意样本分类
 
按文件类型分类,主要分为:
PE文件(Windows平台可执行文件,如exe和dll文件)
ELF文件(Linux平台下的可执行文件)
office文档文件(注意2007版本是个分界点,07版本之前的文档文件本质是二进制文件,之后本质是压缩包)
ps1文件(powershell脚本)
js文件(JavaScript脚本)
VBS文件(vbs脚本)
bat文件(windows批处理文件)
chm文件(微软帮助文档)
lnk文件(链接文件)
hwp文件(韩国office,类似于我国的wps)
...
 
目前比较常见的好像就是这些,在Windows平台下,PE文件是攻击主流,其他的所有恶意样本都统称非PE,通常情况下,现在的样本的流行使用非PE加载PE的方式来攻击,原因是非PE的查杀相对PE来讲较为困难,攻击者可以通过非PE文件在用户的计算机上设置计划任务等操作,来下载后续的PE文件到本地并执行。
 
按行为来分,主要分为:
 
1. Downloader(下载者)  此类木马的主要功能就是下载后续的攻击文件到本地继续执行,此类样本的特点就是小,通常来说,代码也比较简单,就是通过访问一个链接,下载文件保存到本地,然后通过一定的方法启动这个文件。
 
2. Dropper(释放器),此类木马的主要功能是释放并执行后续的攻击文件。就攻击者可能会将恶意的程序加密成为一段数据,然后放到Dropper中,由Dropper解密释放并执行这个文件,这样做的目的是可以起到一定的免杀效果。
 
3. 窃密木马,此类木马的主要功能是收集本地主机的一些机密信息并打包上传,比如收集各大浏览器的缓存目录、隐私目录等,拿到用户保存的密码信息。或是收集本地主机的一些文档文件,最后加密上传到攻击者的邮箱服务器、FTP服务器等。
 
4. 远控木马,远控木马拥有对本地主机的控制权,可以随时通过下发控制指令实现不同的功能。属于在分析中很常见的木马,目前主流的远控木马大多都是由C#编写,并且很多是开源的商业马,在github都可以找到源码实现。
 
5. Ransom(勒索病毒)
 
6. Adware(广告类恶意软件),此类软件通常不像木马那样有侵犯用户隐私的危害,大多数是用于推广广告、篡改用户浏览器主页等。
 
7. 感染型木马,感染型木马较为特殊,遇见的也比较少,可以等后面找一个实际的感染型木马分析。
 
 
0x02 恶意样本分析基本框架流程
 
总的来说,样本分析是有一个固定套路的。通常来说流程如下:
 
1. 文件查壳:看文件是否有壳,有壳则需要脱壳。
 
2. 行为检测:通过行为检测工具,跑出样本的大致行为,通过此步骤,可以对样本的基本功能有个概要的了解,从而确定分析方向,比如在行为检测的时候,监控到样本运行后会访问某个域名,然后我们可以对这个域名进行查询,看该域名是否已经有公开的情报,是否被归类到了某个家族,确定这些信息之后,我们大概可以了解到本次分析的样本的基本类型,比如是下载者、是窃密的、还是远控的。
 
3. 静态分析:静态分析分为导入表分析、字符串分析、代码分析、在导入表分析和字符串分析的时候,可以结合行为检测得到的结果。
 
4. 动态调试:通常来讲,阅读IDA的代码,是分析样本最快的情况,但有时候,一些较为复杂的解密运算、或是样本开辟了新内存等功能,需要使用调试器来协助分析。
 
5. 关联分析:在样本的基本功能分析完成之后,我们需要对样本做关联分析,通常来讲,大部分的样本都是有通信行为的,在关联分析的时候,通常就是以请求的域名、ip等信息进行关联。
 
6. 溯源分析:更深层次的对攻击者进行一个挖掘
 
7. 总结分析报告
 
 
0x03 WindowsAPI基础
 
个人的一点感觉,学习逆向,开发技能可以不用掌握多少,但是开发思想是一定要有的。
 
就一个功能,我们可以不用了解它的具体代码实现,但是我们需要了解它的实现思路。
 
我在没事儿的时候,就经常会想,如果我要写一个远控木马,我应该具备哪些功能,这些功能要如何实现才能更好的提升攻击效率,以及不被检测到。如果我是攻击者,我想要攻击一个企业我需要做哪些准备,需要收集什么信息。想多了之后,在分析木马的时候往往会有意外之喜。 
 
如何辨别程序中的API
 
目前来说,写木马的主流语言还是C和C++,虽然VB和C#的木马越来越多,但想要完全取代应该还有一段时间。其中VB的样本大多数都是Downloader,也就是作为攻击中的前置部分,用于下载后续的木马到本地执行。C#的样本多为远控。说回C和C++,相信大家大学第一门编程课都是C语言,写第一个程序基本上长这样:
#include<stdio.h>int main(){printf("hello ");return 0;}
 
在上面的代码中,虽然只会输出一个hello ,但其实我们已经用到了一个系统函数printf,该函数声明在stdio.h中。
 
我们可以找到stdio.h这个文件,查看printf函数的具体定义:
所以,其实如果我们不需要使用stdio.h中的函数,在C语言开头的那行#include<stdio.h>其实是可以不用写的:
 
但是这个时候我们就会发现,通过纯C语言,我们可以做的东西其实很少。哪怕最简单的接收用户输入,然后输出也需要引用系统的库文件。
 
第二个例子,假如我们想要用原生C语言实现字符串比较的功能,我们可能会这样写:
 
这样可以直接比较str1和str2是否相等,但如果我们还需要统计如果不相等的情况下哪个字符串更大(长)。我们则还需要在其中加入字符串统计的功能:
 
我们也可以调用windowsAPI strcmp实现刚才的功能。
 
通过调用strcmp,我们只需要检查该函数的返回值,就能很好的得到对应的结果。
 
这只是比较简单的例子,Windows封装了超级多的API以供我们在vc和vc++中直接调用,几乎囊括了所有我们在编程中所需要的操作,如文件操作、进程/线程操作、网络请求、注册表操作、加密解密、消息传递等。
 
攻击者在编写恶意代码的时候,通常也会直接调用这些API函数,所以我们在本节中,需要先对WindowsAPI有个大概的了解。
 
想要在程序中辨别使用的API也非常简单,我们在通过IDA对程序进行反汇编之后,程序调用的Windows API在IDA中默认是红色显示:
一般来说,sub_开头的,是IDA自动识别并命名的函数,sub_后面的地址是函数的起始地址。
 
红色显示的函数,就是Windows API函数。
 
如何学习API
 
个人的建议是一边分析,一边记录,然后多翻,多看。
 
有一定编程能力的,可以自己尝试写一些恶意的操作,如文件夹遍历,收集本地信息上传这样的功能。在编写的时候就需要调用很多WindowsAPI函数,在这个过程中,就可以很好的掌握这些API的参数、返回值等信息。
 
如果对编程不是很熟悉的,可以直接分析样本,遇到里面用的API,就去查询它的功能、参数、返回值等。
 
所有的WindowsAPI都可以在微软的官方手册上找到对应的注解,以CreateFile函数为例,在Google搜索一般第一条就是微软的官方注解:
在百度上搜索的话需要自己找一下:
 
在官网中,我们可以看到该函数的使用场景、调用语法、参数详解:(也可以看百度百科)
经过官网的资料我们可以知道该函数可以用于打开或创建控制台、通信资源、目录、磁盘驱动器、文件、邮槽、管道等对象并返回可访问的句柄。且参数1lpFileName就是我们需要打开的文件名。
 
那么我们在分析木马的时候遇到该函数调用,我们就可以根据参数1(最后一个push的参数)的内容,确定攻击者打开/创建的文件名称。
 
我们可以把平时在分析样本时遇到的API都记录到一个excel中,并做好对应的注解,加上自己对这个API的理解,定期的去更新、翻阅这个excel。
 
 
 
- End -
 
 
 

原文地址:https://www.cnblogs.com/xinxihua/p/14332994.html