2021年12月16日木曜日

FridaをもちいたAndroid Malware解析(MoqHao, XLoader)

FridaをもちいたAndroid Malware解析

はじめに

こんにちは。アクティブディフェンス研究所 学生アルバイトの森瑞穂(@morimolymoly) と申します。今回はSmishing(SMSを用いたフィッシング)で猛威を振るうマルウェアファミリMoqHao(XLoader)のC2通信をFridaを使って傍受する手法をご紹介いたします。

MoqHao概要

MoqHaoは詐欺SMSに含まれるURLからダウンロードされるアプリケーションをインストールすることで感染するトロイの木馬型ウィルスです。

我々の観測している範囲内では、Chromeや日本郵便やクロネコヤマトなどのアプリに偽装しています。感染すると詐欺SMSを拡散し、端末内の情報も抜かれます。

これらのアプリケーションはほとんどの機能をパッキングしたペイロードに実装しています。これを後々アンパッキングし、実装された機能を呼び出します。

解析概要

Dynamic InstrumentationツールのFridaを使用して、C2通信に関わる関数を特定、フッキングします。フッキングした後は通信をダンプいたします。

今回はroot化をせず、frida-gadgetをアプリに挿入、アタッチ、フック、解析を行います。

またペイロードのアンパッキング手法は今回は省きます。

Frida-gadgetの配置

frida-gadgetを参考文献[1]のとおりにアプリにインストールします。コツとしてはAndroidManifest.xmlのINTENTとMAINが付与されたactivityのsmaliのクラスのonCreateメソッドの中のlocal変数の数が指定された直後にパッチします。

# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 4

    const-string v0, "frida"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    new-instance p1, Landroid/content/Intent;

    const-class v0, Lgq6h1y4/zfService

次にライブラリを配置します。今回は32bitのfrida-gadgetをインストールすることにしました。

➜  japanpost tree lib/armeabi-v7a/   
lib/armeabi-v7a/
├── libfrida.config.so
├── libfrida.so
└── libka.so

libfrida.config.soは以下のような設定ファイル。

{
  "interaction": {
    "type": "listen",
    "address": "0.0.0.0",
    "port": 27042,
    "on_port_conflict": "fail",
    "on_load": "resume"
  }
}

参考文献[3]に書いてあるものを少し改変しました。

frida-gadgetがロードされたあとにアタッチを待たずに実行を続けるようにしました。

そのあとにビルドして署名して端末に入れます。

apktool d japanpost.apk -o japanpost
apktool b -o jjj.apk --use-aapt2 japanpost/
jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore custom.keystore jjj.apk testkey

端末上でアプリを立ち上げ、 frida-ps でプロセスに Gadget がいるかを確かめていたら動作完了です。

➜  japanpost frida-ps -U | grep Gadget
14258  Gadget

フッキング

実際にフッキングしてみます。

テストのために com.Loader$q クラスのonReceiveメソッドを監視してみます。

まずfrida-gadgetにスクリプトを渡すためのコードを記述します。

次にスクリプトを書きます。トリッキーになっているのは、動的にクラスがロードされる場合、fridaはうまく解釈してクラスを検索できないからです。[4]

import frida
import os
import sys
import argparse

def parse_hook(filename):
     print('[*] Parsing hook: ' + filename)
     hook = open(filename, 'r')
     script = session.create_script(hook.read())
     script.load()
  
  
if __name__ == '__main__':
     try:
         parser = argparse.ArgumentParser()
         parser.add_argument('script', help='Print stack trace for each hook')
         args = parser.parse_args()
  
         session = frida.get_usb_device().attach('Gadget')
         parse_hook(args.script)
         session.resume()
         print('')
         sys.stdin.read()
  
     except KeyboardInterrupt:
         sys.exit(0)
'use strict;'

if (Java.available) {
  Java.perform(function() {

  Java.enumerateClassLoaders({
    onMatch: function(loader){
        Java.classFactory.loader = loader;
        let loaderq;
        try{
          loaderq = Java.use("com.Loader$q");
          console.log("com.Loader$q gotcha!");
          loaderq.onReceive.implementation = function(a, b) {
            console.log("[+] inside method", a, b);
          }
        }catch(error){
          if(error.message.includes("ClassNotFoundException")){
              console.log("\n ClassNotFoundException");
          }
        }
    },
    onComplete: function(){
    }
  });
})
}

これが成功するとonReceiveメソッドが監視できます。

通信に関する関数を特定する

送信・受信のデータを傍受するために、暗号化やアンパッキングを飛ばして入口・出口をフックしてデータを覗き見しましょう。

MoqHaoはC2との通信に、MessagePackとWebsocketを使用します。

また、通信開始時にログに、 skif frame など特徴的な文字列を吐き出します。

これらの文字列をたどっていくと、 com.j クラスにたどり着きます。

送信

送信時には com.jo メソッドが呼ばれることがわかりました。



これをフックして、引数を出力するとC2への送信内容がわかります。また、パラメータがうまく出力できないので、 org.msgpack.core.buffer.MessageBufferUputBytes メソッドをフックして詳細を出力しました。

受信

com.j クラスの中身を解析すると、 受信時にはm メソッドが呼び出されることがわかりました。



これを静的解析すると、MessagePackでアンパックして、その結果を q メソッドに渡すことがわかります。 q メソッドの引数を出力するとC2からの司令を覗くことができます。

通信に関する関数をフックし、傍受する

Fridaを使ったフッキング&解析はトライアンドエラーでフックを仕掛けては外してを繰り返して目的の関数を定めていくことができます。今回も静的解析一発で出力はできませんでした。まずいかにコードを示します。

'use strict;'

if (Java.available) {
  Java.perform(function() {

  Java.enumerateClassLoaders({
    onMatch: function(loader){
        Java.classFactory.loader = loader;
        let msgpack;
        try{
          msgpack = Java.use("org.msgpack.core.buffer.MessageBufferU");

          msgpack.putBytes.implementation = function(d, a, b, c) {
            try {
              const str = a.toString();
              const chars = str.split(',');
              let bstr = "";
              for (const ch of chars) {
                bstr += String.fromCharCode(Number(ch));
              }
              console.log(bstr);
              return this.putBytes(d, a, b, c);
            }catch(error){
              console.log(error);
            }
          }
        }catch(error){
          if(error.message.includes("ClassNotFoundException")){
              console.log("\n ClassNotFoundException");
          }
        }
    },
    onComplete: function(){
    }
  });

  Java.enumerateClassLoaders({
    onMatch: function(loader){
        Java.classFactory.loader = loader;
        let comj;
        try{
          comj = Java.use("com.j");
          console.log("com.j found!");

          comj.o.implementation = function(a) {
            try {
              console.log("to c2:", a);
              return this.o(a);
            }catch(error){
              console.log(error);
            }
          }

          comj.q.implementation = function(a) {
            try {
              console.log("from c2:", a.x());
              return this.q(a);
            }catch(error){
              console.log(error);
            }
          }
        }catch(error){
          if(error.message.includes("ClassNotFoundException")){
              console.log("\n ClassNotFoundException");
          }
        }
    },
    onComplete: function(){
    }
  });
})
}

概ね先述の通りのフックになっていることがわかるかと思います。

com.jq メソッドのフックの console.log 時に、 a.x() と呼び出していますが、これはトライアンドエラーの結果で、単純に引数を出力するとエラーになるので(エラーというよりアウトプットがうまくいかない)、toStringなメソッドが生えていないか調べ、返り値の型がStringな関数に焦点を当て呼び出しています。toStringなラベルが無いのはおそらく難読化によるものであると考えられます。

C2からの司令

今回はSMSを送信させる司令である sendSMS を観測いたしましたので以下に内容を示します。

from c2: {"jsonrpc":"2.0","method":"sendSms","id":89,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://locmwfecfw.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":90,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://locmwfecfw.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":91,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lrxakntmnw.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":92,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":93,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":94,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":95,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://mbbmrqpdik.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":96,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":97,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":98,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://mbbmrqpdik.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":99,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":100,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":101,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://mbbmrqpdik.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":102,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://mbbmrqpdik.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":103,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}

生々しく配信先の電話番号と文面が記されています。

おわりに

Fridaを使ったメソッドフッキングでC2通信の内容を傍受いたしました。

Fridaの使用方法紹介からC2の司令内容のダンプまで紹介いたしました。

今回の検体は大まかにはMessagePackを使用して簡素な通信を行っていましたが、今後暗号化が強化された際に今回の手法は効果を大きく発揮することでしょう。また、この手法の強みは出口と入口さえわかれば暗号化・復号化をスキップして目的の値を取得できるところにあります。

IoC

  • 765119852dc93c3a5d397cbaa167ac148856dba9773062626ed4aeb9674f860c
  • 45[.]114.129.50:28877
  • https://m.vk[.]com/id674309800?act=info
  • https://m.vk[.]com/id674310752?act=info
  • https://m.vk[.]com/id674311261?act=info

Special Thanks!

Nyleon88氏(https://twitter.com/Nyleon88) - C2配信の挙動についての情報などを提供していただきました

参考文献

[1] https://koz.io/using-frida-on-android-without-root/

[2] https://www.trustedsec.com/blog/mobile-hacking-using-frida-to-monitor-encryption/

[3] https://frida.re/docs/gadget/

[4] https://serializethoughts.com/2021/05/07/frida-java-classloaders

1 件のコメント:

  1. Video slots typically embrace bonus games, where players can win free spins and returns on their bets. Sometimes the bonus spherical is simple, but typically extra complicated video slots will provide absolutely themed hidden bonus games. If a recreation gives players the chance to win a progressive jackpot then it’s typically dafabet found within one of the bonus games.

    返信削除