가상화 난독화 NMMP를 의미로 복원하는 방법 - PART2
이 글은 보안 분석가, 리버스 엔지니어, 안드로이드 앱의 내부 동작 원리에 깊은 관심이 있는 개발자를 대상으로 합니다.
이 글은 보안 분석가, 리버스 엔지니어, 안드로이드 앱의 내부 동작 원리에 깊은 관심이 있는 개발자를 대상으로 합니다.
안녕하세요. 분석팀 악성코드 분석 파트의 신입 Jiyong입니다.
설렘 반, 걱정 반으로 첫 출근한 지 엊그제 같은데 벌써 5주 차가 되었네요.
사실 입사 초기에는 “신입사원에게는 보통 연습용 과제가 주어지지 않을까?” 막연히 생각했습니다. 하지만 저희 팀의 연습은 실전 그 자체였습니다. 제가 입사 3주 차에 받은 과제는 바로 ‘정부24’ 앱을 위장한 실제 악성 앱 샘플이었습니다.
샘플 분석 과정에서 저를 가장 놀라게 했던 것은 이 앱이 가진 집요한 생존 본능이었습니다. 도대체 어떤 기술들이 숨겨져 있길래 이렇게까지 끈질길 수 있었을까요? 이 글에서는 수많은 사용자를 속이기 위해 만들어진 이 악성 앱이 사용자의 기기에서 살아남기 위해 얼마나 끈질긴 지속성 유지 장치들을 숨겨두었는지에 대한 기술적인 부분을 정리하고자 합니다.
동시에 신입사원에게도 실제 샘플을 맡기고 스스로 분석을 주도하게 하는 저희 팀의 문화와 그 속에서 제가 경험한 5주간의 생생한 기록이기도 합니다.
공격자들은 무엇을 노리고 이 앱을 선택했으며, 또 팀은 어떤 점 때문에 제게 이 샘플을 첫 과제로 주었는지 그 이유를 설명해드리고자 합니다.
이 앱이 등장하던 시점은 코로나 19 펜데믹의 영향으로 비대면과 디지털 인증이 일상이 된 시대였습니다. 이때는 재난 지원금 신청, 백신 접종 증명 등 ‘정부 24’와 같은 공공 서비스를 이용해야만 했습니다.

[그림 1] 피싱 문자 사례
공격자에게는 이보다 더 좋은 미끼는 없었을 것입니다.
이처럼 정부에서 온 공식 알림으로 위장한다면 사용자는 의심할 틈도 없이 악성 앱을 설치하게 될 것입니다.
또한 이 샘플은 복잡한 난독화 뒤에 숨는 대신 악성 앱이 갖춰야 할 기본 공식을 가감 없이 보여주는 가장 좋은 교재였습니다. 덕분에 저는 코드 해석에 시간을 쏟기보다 공격자가 어떤 의도로 로직을 설계했는지 그 생각을 읽어내는 데 집중할 수 있었습니다.
과도한 권한 요구, 개인정보 탈취 로직, SMS 가로채기, 그리고 이 글의 핵심 주제인 지속성 유지를 위한 다양한 장치들까지. 마치 악성 행위의 총집합 같았습니다.
팀은 제게 정답을 찾아오라고 한 것이 아니라 실제 필드에서 마주할 대표적인 문제를 스스로 파헤쳐 볼 기회를 주신 것이었습니다. 저는 이 앱을 통해 실제 공격자들이 어떤 로직으로 사용자를 속이고 살아남는지, 그 기본기를 확실하게 배울 수 있었습니다.
막상 실제 악성 앱 샘플(Apk 파일)을 마주하니 어디서부터 시작해야 할지 잠시 막막했습니다. CTF 문제처럼 “Flag를 찾으시오” 같은 친절한 안내는 없으니까요. 고민하다가 우선 이 앱이 도대체 무슨 일을 하려 하는지 살펴보기로 했습니다.
분석의 첫걸음은 앱의 지도와도 같은 AndroidManifest.xml 파일을 열어보는 것이었습니다. jadx 디컴파일러로 권한 목록을 살펴보자마자, 이 앱이 시스템에 요구하는 권한 목록이 상식적이지 않다는 것을 바로 알 수 있었습니다. 정상적인 정부24 앱의 기능이라고 보기에는 너무나 과한 권한들을 요구하고 있었기 때문입니다.

[그림 2] 과한 권한 요구
이처럼 과한 권한 요청은 악성 행위를 수행하는 코드 추적의 확실한 실마리가 됩니다.
정부24 앱이 사용자의 문자메시지를 전부 읽을 필요가 있을까요? 저는 이것이 공격자의 의도를 파악할 핵심 키라고 확신했습니다. 이 권한을 사용하는 코드를 추적하는 것은 당연한 수순이었고 어렵지 않게 범인을 찾을 수 있었습니다.
이 앱은 특정 서비스(Service)가 백그라운드에서 실행되며 사용자의 모든 SMS 메시지를 감시합니다. 해당 서비스는 문자가 수신되면 해당 메시지를 하드코딩된 외부 서버(C&C 서버)로 몰래 전송합니다.

[그림 3] C&C 서버로 수집한 메시지 전송
이런 행위는 명백한 악성 행위입니다. 이 앱의 목적은 ‘정부24’의 기능을 제공하는 것이 아니라, 사용자의 개인 정보를 훔치는 스파이웨어임이 분명합니다.
| 분류 (Category) | 악성 행위 (Action) | 연관 메서드 (Component / Method) |
|---|---|---|
| 정보 탈취 | C&C 서버로 기기 정보 전송 | ProcessCommand.uploadDeviceInfo |
| C&C 서버로 주소록(연락처) 전송 | ProcessCommand.uploadContact |
|
| C&C 서버로 모든 SMS 메시지 전송 | ProcessCommand.uploadSMS |
|
| C&C 서버로 설치된 앱 목록 전송 | ProcessCommand.uploadApp |
|
| C&C 서버로 갤러리 이미지 전송 (특히 카메라/스크린샷) |
ProcessCommand.uploadImages |
|
| 수신되는 SMS 메시지 실시간 가로채기 및 C&C 전송 | SmsReceiver |
|
| 원격 제어 | C&C 서버 연결 및 원격 명령 수신 | MainService.initSocket (WebSocket) |
| C&C 명령을 통한 SMS 메시지 발송 (스미싱) | ProcessCommand.handleSendSms |
|
| C&C 서버 주소(IP)를 Github에서 동적으로 수신 (하드코딩된 서버 주소가 다운된 경우) |
HttpUtils.getHtmlData( AppConstants.URL_CHECK_IP_GIT) |
이를 파악하고 나서, 위 표에 정리된 것과 같은 다른 악성 행위들을 하나씩 추가로 식별해 나갔습니다. 그렇게 개인 정보 탈취 로직을 대부분 찾아내고 나니 이제 정말 분석이 끝난 것 같았습니다. 그런데 팀원들은 “그게 다가 아닐 거예요. 남은 컴포넌트들을 더 분석해보세요.“라고 조언해주셨습니다.
다시 코드를 살펴보자 저는 더 놀라운 것들을 발견하기 시작했습니다. 정보를 훔치는 기능보다 오히려 ‘어떻게든 살아남기 위한 코드’들이 훨씬 더 많고 교묘했습니다. 그 모습은 마치 이렇게 외치는 것 같았습니다.
이것이 바로 제가 이 글에서 전해드리고자 하는 ‘지속성 유지’입니다.
앞선 챕터에서 이 앱의 목적이 정보 탈취임을 밝혔습니다. 하지만 공격자 입장에서 앱을 통한 정보 탈취를 지속적으로 수행해야 하는데 힘들게 설치시킨 스파이웨어가 스마트폰을 껐다 켜거나, 사용자가 메모리 정리를 할 때마다 종료된다면 매우 비효율적이겠죠.
그래서 이 악성 앱은 정보 탈취 로직보다 오히려 어떻게든 살아남기 위한 코드. 즉, 지속성 유지에 훨씬 더 많은 공을 들였습니다. 제가 분석 과정에서 발견한 이 앱의 생존 장치들을 하나하나 소개해 드립니다.
악성 앱이 생존하기 위해서는 사용자의 눈을 피하는 동시에, 까다로운 안드로이드 OS의 시스템을 속여야합니다. 이 앱에서 사용자와 안드로이드 OS 시스템을 동시에 속이기 위해 사용된 여러 전략 중, 제가 생각한 가장 핵심적인 세 가지 전략은 다음과 같습니다.
이 앱은 사용자가 자신의 존재를 인지하고 삭제하지 못하도록 처음 실행되는 순간 앱은 아래 코드를 실행시켜 앱 아이콘을 투명 아이콘으로 교체합니다. 이 코드가 실행되면 스마트폰 홈 화면(런처)에서 앱 아이콘이 즉시 은닉되어 사용자는 앱이 설치되었다는 사실조차 인지하기 어렵게 됩니다.

[그림 4] 아이콘 은닉 코드
사용자의 눈을 피하는 데 성공했더라도 메모리가 부족해지면 안드로이드 OS가 앱을 강제로 종료시킬 수 있습니다. 이 앱은 OS를 기만하기 위해 PlayermusicService를 이용합니다.
해당 서비스는 무음으로 녹음되어 있는 오디오 파일을 무한 반복 재생시킵니다. 사용자는 아무 소리도 들리지 않지만 OS는 이 앱을 실행 중인 앱으로 판단하게 됩니다. 이런 행위로 자신의 프로세스를 포그라운드 상태로 유지시켜 LMK(Low Memory Killer)의 강제 종료 대상에서 회피합니다.

[그림 5] 무음 오디오 재생
무음 오디오와 마찬가지로 OS를 속이는 두 번째 장치입니다. OnePixelReceiver가 화면이 꺼지는 이벤트를 감지하면, 1x1 픽셀 크기의 보이지 않는 화면을 띄웁니다.
단 1픽셀이라도 화면에 무언가를 그리고 있으면 안드로이드 OS는 이 앱을 백그라운드 상태가 아닌 포그라운드 상태로 간주합니다. 이 역시 OS의 메모리 정리 대상에서 벗어나기 위한 전략입니다.

[그림 6] 1픽셀 윈도우 코드
| 트리거 | 처리 컴포넌트 | 수행 동작 |
|---|---|---|
| MainService 실행 시 |
MainService( PowerManager, WifiManager)
|
WakeLock과 WifiLock을 획득하여, 기기 화면이 꺼져도 CPU와 WiFi가 잠들지 않도록 C2 통신 등을 지속적으로 유지합니다.
|
| 앱 초기 실행 시 |
SplashActivity / AndroidManifest.xml
|
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 권한을 요청하여, 안드로이드의 배터리 절약 기능으로 인해 앱이 중지되는 것을 방지합니다.
|
이 앱은 프로세스 종료 회피 방어선들을 모두 뚫고 앱이 종료되더라도, 즉시 혹은 주기적으로 되살아나게 합니다. 종료라는 상황을 해결하기 위해 마치 좀비처럼 되살아나는 장치들을 이중, 삼중으로 갖추고 있었습니다.
그 중 가장 집요하다고 느꼈던 두 가지 핵심은 다음과 같습니다.
이 앱은 MainService와 RemoteService라는 두 개의 핵심 서비스를 동시에 실행시키며, 이 둘은 서로가 서로를 감시합니다. 서로가 바인딩되어 서로의 상태를 확인하다가, 만약 MainService가 비정상적으로 종료되어 연결이 끊어지면 즉시 MainService를 다시 시작시킵니다.
이처럼 어느 하나를 종료시켜도 다른 하나가 즉시 살려내는 전형적인 Watchdog 패턴을 사용해 생존을 보장합니다.

[그림 7] 메인 서비스 종료 시 재실행
이 앱은 JobScheduler를 통한 주기적 감시 행위를 하고 있습니다. 이와 같은 주기적 감시와는 별개로 AlarmManager를 이용한 훨씬 더 공격적인 재실행 장치가 존재했습니다. MainService는 AlarmReceiver를 이용해 10초마다 강제 알람을 등록합니다.
이 알람이 울릴 때마다 서비스가 살아있는지 확인하고 종료되었다면 다시 실행시켜 사실상 10초 이내의 재실행을 보장합니다.

[그림 8] 지정된 시간에 AlarmReceiver를 실행
위의 두 가지 핵심 장치가 실패할 경우를 대비해 앱은 아래 표와 같이 대부분의 상황에 대한 재실행 로직을 갖추고 있었습니다.
| 트리거 | 처리 컴포넌트 | 수행 동작 |
|---|---|---|
| 스마트폰 재부팅 완료 | MainReceiver |
MainService를 자동으로 다시 실행시킵니다. |
| 주기적인 감시 (2초마다) |
MyJobService |
MainService와 RemoteService가 종료되었는지 감시하고 즉시 재실행합니다.( setPersisted(true)로 재부팅 후에도 작업이 유지됨)
|
| 사용자의 홈 버튼 / 최근 앱 버튼 |
HomeKeyEventReceiver |
사용자가 앱을 빠져나가려 하면, MainService를 즉시 실행시켜 백그라운드에 상주하도록 만듭니다. |
| 네트워크 상태 변경 | NetworkReceiver |
와이파이/데이터가 다시 연결되는 순간, C2 서버와의 WSManager 웹소켓 연결을 자동으로 복구합니다. |
재부팅, OS의 메모리 관리, 사용자의 직접 삭제 시도, 심지어 네트워크 단절까지, 종료라고 부를 수 있는 거의 모든 시나리오에 대한 대응책을 갖추고 있었습니다. 덕분에 저는 입사 5주 만에 악성 앱이 활용하는 지속성 유지 기법의 총집합를 맛볼 수 있었습니다.
하지만 이 악성 앱을 분석하는 과정이 처음부터 순탄했던 것만은 아닙니다. 이 복잡한 구조를 신입사원인 제가 파악할 수 있었던 것은 단순히 제 노력 때문만이 아니었습니다.
제가 ‘정부 24’ 앱을 분석할 수 있었던 이유는 바로 저희 팀의 문화 덕분이었습니다.
저희 팀은 신입에게 정답이 정해진 연습 문제가 아니라 스스로 파헤쳐 볼 실전 문제를 줍니다. 분석의 범위와 깊이를 스스로 결정할 수 있도록 자율을 부여하고, 그 과정을 해낼 수 있을 것이라는 신뢰를 보여줍니다.
문제를 해결하는 과정에서 제가 길을 잃을 때마다 팀원분들은 정답 대신 방향을 제시해 주셨습니다.
제가 정보 탈취 로직 몇 개를 찾고 분석이 끝난 것 같다고 말씀드렸을 때, 팀원들은 다른 컴포넌트들도 분석해보라고 조언해 주셨습니다.
이것이 바로 저희 팀이 분석하는 방식입니다. 단순히 ‘악성이다/아니다’라는 결론을 내는 것을 넘어, 공격자가 어떻게 만들었고, 무엇을 노렸으며, 왜 이런 방식을 택했는지 그 의도를 파악하는 데 집중합니다.
지난 5주간, 저는 악성코드를 분석했지만 사실은 팀이 일하는 방식을 더 깊게 배운 것 같습니다. 실전을 통해 배우고 스스로 답을 찾게 하는 과정을 신뢰해 주는 문화 속에서 첫 번째 분석 리포트를 무사히 완성할 수 있었습니다.
첫 과제를 통해 분석가의 관점이라는 값진 기본기를 배웠습니다. 앞으로 더 교묘하고 새로운 위협들을 찾아내어 여러분께 공유하는 한층 더 성장한 분석가로 다시 인사드리겠습니다.
긴 글 읽어주셔서 감사합니다.
이 글은 보안 분석가, 리버스 엔지니어, 안드로이드 앱의 내부 동작 원리에 깊은 관심이 있는 개발자를 대상으로 합니다.
이 글은 보안 분석가, 리버스 엔지니어, 안드로이드 앱의 내부 동작 원리에 깊은 관심이 있는 개발자를 대상으로 합니다.
안드로이드 악성 앱을 분석하는 일은 악성 앱 개발자와 리버스 엔지니어 간의 끊임없는 싸움입니다. 개발자는 소스 코드와 핵심 로직을 보호하기 위 …
안드로이드 악성 앱을 분석하는 일은 악성 앱 개발자와 리버스 엔지니어 간의 끊임없는 싸움입니다. 개발자는 소스 코드와 핵심 로직을 보호하기 위해 다양한 난독화 기법을 적용하고, 리버스 엔지니어는 이를 파훼하여 내부 구조를 분석해야 하죠. 이 싸움의 최전선에 있는 기술 중 하나가 바로 가상화 기반 난독화입니다.