인기 있는 중국산 프로젝터에서 Android 악성코드 역공학 (Reverse engineering) 하기
요약
저가형 중국산 프로젝터에서 발견된 의심스러운 DNS 트래픽을 추적하여 Android 악성코드를 역공학하는 과정을 다룹니다. 사용자는 ADB와 jadx를 활용해 가짜 HTC 패키지를 식별하고, 루팅을 통해 시스템 앱을 비활성화하며 분석을 진행했습니다.
핵심 포인트
- 저가형 IoT 기기에서 의심스러운 외부 도메인(phoning home) 통신이 발생할 수 있음
- 가짜 네임스페이스(예: com.htc.)를 사용하여 악성 패키지를 위장하는 수법 확인
- ADB와 jadx를 활용한 Android 앱 패키지 추출 및 정적 분석 방법
- 시스템 레벨 앱 제어를 위한 기기 루팅 및 패키지 비활성화 과정
Claude Code를 이용한 Android 악성코드 역공학 (Reverse engineering)
2026년 2월 5일
AliExpress에서 구매한 35달러짜리 프로젝터를 연결하고 침실 벽에 비추었습니다. Wi-Fi에 연결한 지 몇 분 지나지 않아, 저의 Pi-hole에 경고가 떴습니다.
o.fecebbbk.xyz
impression.appsflyer.com
저는 브라우저를 열지도, 앱을 설치하지도, 홈 화면을 벗어나지도 않았습니다. 프로젝터가 스스로 외부와 통신(phoning home)하고 있었던 것입니다. 그리고 이어서:
usmyip.kkoip.com
이게 무엇인지 당시에는 알지 못했습니다. 곧 알게 되겠지만 말입니다.
Magcubic HY300 Pro+와 같은 저가형 프로젝터들이 TikTok, Amazon, Temu, AliExpress를 휩쓸고 있습니다. Reddit의 프로젝터 커뮤니티는 낮은 화질부터 완전한 고장까지 다양한 불만을 토로하며 이 제품들을 그리 좋게 보지 않습니다. 저는 8000 루멘(의심스러움), 자동 키스톤 교정(automatic keystone correction), 그리고 "4K 지원"을 약속하는 약 35달러(USD)의 제품을 구매했습니다. TV 박스 사촌들처럼 불쾌한 악성코드(malware)가 포함되어 있을 것 같다는 예감이 들었는데, 솔직히 그 점이 재미의 일부이기도 했습니다.
전원을 켰을 때의 경험은 예상보다 더 전문적이었습니다. Android 11 (API 30), 프로덕션 빌드(production build, 테스트 키로 서명되지 않음!), 그리고 기본 상태에서 루팅(rooted)되어 있지 않았습니다. 하지만 세련된 런처(launcher)도 Pi-hole이 이미 명확히 보여준 밑바닥의 수상함을 완전히 가릴 수는 없었습니다.
adb와 jadx를 무기로, 저는 사전 설치된 앱들을 조사하기 시작했습니다. 첫 번째 적신호(red flag)는 HTC에서 만든 것이 아닌 기기에 수많은 com.htc. 패키지들이 존재한다는 것이었습니다. 이 기기는 Hotack(Magcubic와 같은 브랜드명으로 판매됨)이라는 회사에서 만든 것입니다. 얄팍한 변장이었습니다.
가짜 com.htc. 네임스페이스(namespace)와 의심스러운 DNS 트래픽 사이에서, 저는 이 패키지들이 원인이라는 강한 직감이 들었습니다. 이를 비활성화하려면 먼저 루트 권한(root access)이 필요했습니다. 이것들은 루트 권한 없이는 건드릴 수 없는 시스템 레벨(system-level) 앱들이기 때문입니다. 저는 XDA Forums의 튜토리얼을 따라 기기를 루팅한 후, 수상해 보이는 모든 패키지를 비활성화했습니다:
adb shell pm disable-user --user 0 com.hotack.silentsdk
adb shell pm disable-user --user 0 com.htc.eventuploadservice
adb shell pm disable-user --user 0 com.htc.expandsdk
...
5개의 패키지가 비활성화되었습니다. 의심스러운 DNS 쿼리가 중단되었습니다. 이를 통해 이들이 범인임을 확인했지만, 정확히 무엇을 하고 있는지 알고 싶었습니다. 그래서 APK 파일들을 추출했습니다:
adb pull $(adb shell pm path com.hotack.silentsdk | cut -d: -f2) silentsdk.apk
adb pull $(adb shell pm path com.htc.eventuploadservice | cut -d: -f2) eventuploadservice.apk
adb pull $(adb shell pm path com.htc.expandsdk | cut -d: -f2) expandsdk.apk
...
저는 jadx를 사용하여 com.hotack.silentsdk를 분석했습니다.
ProGuard/R8 난독화 (Obfuscation)로 인해 클래스 이름은 a.java, b.java, f.java와 같이 단일 문자로 축소되어 있었고, 암호화된 문자열과 의도적으로 혼란을 주는 제어 흐름 (Control flow)이 포함되어 있었습니다. 한동안 수동으로 코드를 추적한 끝에 대략적인 형태를 파악할 수 있었습니다. 즉, 부팅 시 시작되어 원격 서버에 접속하고 무언가를 다운로드하는 서비스였습니다. 하지만 난독화된 문자열을 수동으로 복호화하고, 리플렉션 체인 (Reflection chains)을 따라가며, C2 프로토콜을 매핑하는 작업은... 며칠이 걸릴 일이었습니다.
혼자서 무식하게 힘으로만 해결할 수는 없었습니다.
저는 소프트웨어 엔지니어링 작업에 Claude Code를 사용해 왔으며, 결과는 대체로 긍정적이었습니다. 저는 이것이 단순히 역공학 (Reverse engineering)의 지루한 부분을 가속화하는 것 이상의 역할을 할 수 있을 것이라 의심했습니다. 저는 jadx로 APK를 디컴파일 (Decompile)하고 소스 코드를 디렉토리에 덤프 (Dump)한 뒤, 다음과 같은 프롬프트를 입력했습니다:
# Android 프로젝터 악성코드 조사
당신은 악성코드가 사전 설치되어 있다고 의심되는 Android 기반 프로젝터를 조사하고 있습니다. 당신은 ADB를 통해 루트 (Root) 권한을 가지고 있습니다.
...
그런 다음 실행하도록 두었습니다.
Claude Code의 첫 번째 움직임은 com.hotack.silentsdk 곳곳에 흩어져 있는 XOR 암호화된 문자열을 찾아 복호화하는 것이었습니다. 민감한 문자열(URL, 알고리즘 이름, 파일 경로 등)은 암호화된 바이트 배열 (Byte arrays)로 저장되어 있었으며, 실행 시점에 회전 XOR 암호 (Rotating XOR cipher)를 사용하여 복호화되었습니다:
// a/a.java:834 - XOR 문자열 복호화
public static String g(byte[] bArr, byte[] bArr2) {
int length = bArr.length;
...
전형적인 난독화 (Obfuscation) 방식입니다. 이는 정적 문자열 분석 (Static string analysis)을 방해하지만, 패턴을 파악하고 나면 아주 쉽게 되돌릴 수 있습니다. 저는 Claude Code가 이를 감지하고 저에게 어떻게 할지 물어볼 것이라고 예상했습니다. 하지만 Claude Code는 별도의 프롬프트 없이도, 디컴파일된 코드베이스 전체를 탐색하며 이 함수에 대한 모든 호출을 자동으로 복호화하는 Python 스크립트를 작성했습니다:
def xor_decode(data_bytes, key_bytes):
"""악성코드의 회전 XOR 암호 (Rotating XOR cipher)를 재현합니다."""
result = []
...
단 몇 초 만에 전체 코드베이스가 낱낱이 드러났습니다:
| 암호화된 호출 (Encrypted Call) | 복호화된 값 (Decrypted Value) | 용도 (Purpose) |
|---|---|---|
g({-99,127,58,...}, {-4,15,83,...}) | api.pixelpioneerss.com | C2 도메인 |
g({125,61,58,...}, {21,73,78,...}) | https:// | 프로토콜 접두사 |
g({35,-3,-58,-76}, {69,-52,-10,...}) | f101 | 캠페인 식별자 |
g({-78,124,-112,...}, {-26,49,-62,...}) | TMRXwWJu3G5 | 페이로드 데이터 디렉토리 |
g({-101,-100,115,...}, {-45,-16,4,...}) | HlwET4RJQV | SharedPreferences 파일명 |
g({-83,-92,-19,...}, {-50,-52,-128,...}) | chmod 777 | 쉘 명령어 (Shell command) |
api.pixelpioneerss.com은 우리의 C2 서버입니다. chmod 777은 다운로드된 파일에 실행 권한을 부여합니다. 분석용 SDK가 아닙니다.
제가 수작업으로 했다면 몇 시간이 걸렸을 일이 단 몇 분 만에 끝났습니다. 그리고 이것은 시작에 불과했습니다. Claude Code는 저를 기다리지 않고 이미 다음 APK로 넘어가고 있었습니다.
디컴파일된 각 APK를 분석하면서, Claude Code는 벤더(Vendor) 악성코드의 조직적인 세트를 매핑했습니다:
| 패키지 (Package) | 역할 (Role) | C2 인프라 (C2 Infrastructure) |
|---|---|---|
com.hotack.silentsdk | 원격 접속 트로이목마 (RAT) / 드롭퍼 (Dropper) | api.pixelpioneerss.com |
com.htc.eventuploadservice | 기기 텔레메트리 유출 (Device telemetry exfiltration) | event-api.aodintech.com |
com.htc.expandsdk | 광고 삽입 및 악성코드 지속성 (Ad injection & malware persistence) | pb-api.aodintech.com |
com.htc.htcotaupdate | OTA 업데이트 (참고로 HTTP를 통해 수행됨) | ota.triplesai.com |
com.htc.storeos | 조용한 앱 설치기 (완전히 악의적이지는 않음) | store-api.aodintech.com |
aodintech.com 인프라는 명백히 상업적인 광고/추적 네트워크였습니다. triplesai.com에 있는 OTA 서버는...
암호화되지 않은 HTTP를 통해 업데이트를 전송했으며, 로우 IP (raw IP) 폴백 (139.199.190.220, Tencent Cloud)을 사용했습니다. 인증서 피닝 (Certificate pinning)도, 서명 검증 (Signature verification)도 없었습니다. 업데이트 과정에 대한 중간자 공격 (Man-in-the-middle attack)은 매우 쉬웠을 것입니다.
하지만 핵심은 com.hotack.silentsdk였습니다. 이것은 흔히 볼 수 있는 단순한 애드웨어 (Adware) 수준을 훨씬 넘어선, 본격적인 원격 접속 트로이 목마 (Remote Access Trojan, RAT)였습니다.
저는 Claude Code를 SilentSDK에 구체적으로 지정하여 다음과 같이 명령했습니다:
디컴파일된 silentsdk 코드베이스를 주의 깊게 조사하도록 Explore 에이전트를 보내주세요. 에이전트가 조사를 마치면, 에이전트가 알려준 내용을 바탕으로 silentsdk의 동작과 행위를 심층적으로 파헤치세요. 마지막으로 그 목적과 작동 방식(프로토콜, 난독화 (Obfuscation), API 호출에서의 은닉을 통한 보안 (Security-by-obscurity) 등)에 대한 상세 정보가 담긴 SILENTSDK.md 파일을 작성하세요.
그 결과 전체적인 그림을 얻을 수 있었습니다.
SilentSDK의 Android 매니페스트 (Manifest)는 코드를 살펴보기도 전에 많은 것을 알려줍니다:
android:sharedUserId="android.uid.system"
android:foregroundServiceType="systemExempted"
android:usesCleartextTraffic="true"
android.uid.system은 이 앱이 핵심 Android 서비스와 동일한 시스템 수준의 권한 (System-level privileges)으로 실행됨을 의미합니다. 이는 APK가 제조사의 플랫폼 인증서 (Platform certificate)로 서명된 경우에만 가능합니다. 이 앱은 기기에 몰래 설치된 것이 아니라, 처음부터 내장되어 있었습니다.
이 악성코드는 우선순위 999(최댓값에 가까운 수치)로 부트 리시버 (Boot receiver)를 등록하여, 거의 모든 다른 앱보다 먼저 시작되도록 보장합니다:
// BootReceiver.java
public final void onReceive(Context context, Intent intent) {
if (Build.VERSION.SDK_INT >= 26) {
...
MyService는 systemExempted 포그라운드 서비스 (Foreground service)로 실행됩니다. 이는 Android의 백그라운드 제한 사항을 면제받아 사실상 종료가 불가능함을 의미합니다. 또한 START_STICKY를 반환하므로, Android는 이 서비스가 중단될 경우 자동으로 재시작합니다. 일단 실행되면, 네트워크 연결이 가능해지는 즉시 C2 통신을 트리거하는 NetworkCallback을 등록합니다.
Claude Code는 b/n.java와 b/a.java에 있는 난독화된 코드를 추적했습니다.
그리고 전체 C2 프로토콜을 역공학(Reverse engineering)했습니다.
악성코드가 외부와 통신할 때, 무작위 경로를 가진 난독화된 URL을 생성합니다:
// b/n.java:39 - 무작위 서브도메인 경로를 사용한 URL 구성
public static String b(String str) {
Random random = new Random();
...
각 요청은 https://api.pixelpioneerss.com/aB3k9mP2s와 같은 고유한 URL로 전송되어, 경로만으로는 트래픽을 식별하기 어렵게 만듭니다.
요청 페이로드(payload)는 장치 지문(Device fingerprint) (UUID, 기기 브랜드, 모델, IMEI, Android ID 및 타임스탬프의 SHA-256 해시), 패키지 이름, 캠페인 키(f101), 그리고 SDK 버전을 포함하는 JSON 객체입니다. 이것은 무작위 키와 IV를 사용하여 AES-128-CBC로 암호화된 후, 바이너리 메시지로 패킹됩니다:
요청 형식:
┌────────────┬────────────────┬──────────┬──────────┐
│ Version(4) │ Ciphertext(N) │ IV(16) │ Key(16) │
...
네, 암호화 키가 평문으로 암호문 뒤에 붙습니다. 이
시행착오를 통해 발견한 미묘한 차이점이 하나 있었습니다. 응답 형식(response format)이 요청 형식(request format)과 다르다는 점이었습니다. 요청에는 4바이트 버전 필드가 포함되지만, 응답에는 포함되지 않습니다. 클라이언트를 실행하고, 에러가 발생하는 것을 확인하고, 역컴파일된 복호화 메서드(b/a.java:a())를 다시 읽고, 구현을 수정하는 과정을 거쳐서야 이를 알아낼 수 있었습니다. 그 과정이 실시간으로 일어나는 것을 지켜보는 것은 정말 대단한 경험이었습니다.
C2(Command and Control)가 응답했습니다. 살아있었습니다:
{
"code": "0000",
"data": {
...
저는 한동안 f 필드를 뚫어지게 쳐다보았습니다. "United States of America/California/Stanford." e 필드에는 제 IP 주소가, f 필드에는 제 지리적 위치(geolocation)가 적혀 있었습니다. C2는 살아있었으며, 제가 정확히 어디에 있는지 인지하고 있었고, MD5 해시(b 필드)와 1시간의 TTL(d 필드: 3,600,000 밀리초)이 포함된 다음 단계 페이로드(a 필드)를 제공하고 있었습니다.
이 지점이 바로 SilentSDK가 "수상한 추적용 SDK"에서 "완전한 원격 액세스 트로이목마 (RAT, Remote Access Trojan)"로 넘어가는 단계입니다. 악성코드는 DEX (Dalvik Executable)를 포함하는 JAR 파일을 다운로드하고, MD5 해시를 검증한 뒤, 정적 탐지(static detection)를 피하기 위해 리플렉션 (Reflection)을 사용하여 DexClassLoader를 통해 이를 동적으로 로드합니다:
// b/k.java - 리플렉션을 통한 DEX 로딩
public final Class b(Context context, int version, String dexPath, Object classLoader) {
Class<?> dexLoaderClass = Class.forName(
...
심지어 "dalvik.system.DexClassLoader" 문자열조차 이 클래스 이름을 플래그(flag)하는 도구들을 피하기 위해 조각들로 나누어 조립되어 있습니다. 로드된 클래스는 (Context, Bundle) 시그니처를 가진 리플렉션을 통해 호출되는데, 이는 C2가 원하는 어떠한 코드라도 시스템 권한으로 실행하여 전달할 수 있음을 의미합니다.
Claude Code가 2단계 페이로드(70 KB JAR, 151 KB 추출된 DEX)를 다운로드하여 분석했습니다: OooO0O0 및 OooO00o와 같은 이름을 가진 47개의 심하게 난독화된 (obfuscated) 클래스들, 그리고 15~30분마다 두 번째 C2 서버(bur.thedynamicleap.com)에 체크인하며 필요에 따라 추가 플러그인을 다운로드, 설치, 업데이트 및 실행할 수 있는 컴포넌트 관리 프레임워크가 발견되었습니다.
3단계 아키텍처:
Stage 1 (Dropper): com.hotack.silentsdk
– 사전 설치됨, api.pixelpioneerss.com과 연락함, Stage 2를 다운로드함
Stage 2 (Framework): magic.v6037
– 다운로드된 페이로드 (payload), 컴포넌트 관리, bur.thedynamicleap.com으로의 주기적인 체크인 (check-ins)
Stage 3 (Plugins): 필요에 따라 다운로드되는 모듈형 컴포넌트—kkoip.com과 관련된 항목을 포함함
초기 DNS 모니터링에서 발견되었던 그 kkoip.com 도메인입니다. 그것이 실제로 무엇인지 알아낼 차례입니다.
usmyip.kkoip.com을 기억하시나요? 제가 프로젝터를 만지기도 전에 Pi-hole에 나타났던 그 도메인 말입니다.
KKOIP는
AI 자동 생성 콘텐츠
본 콘텐츠는 HN OpenAI Codex의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기