vendredi 30 août 2013

Sécurité des applications Android

Depuis Hack In Paris 2012 et la présentation de Georgia Weidman (@georgiaweidman) sur la sécurité Android, j'ai envie moi aussi de développer ma propre application.

Eh bien c'est fait!

Mon objectif est double:
  • Vérifier la force des contre-mesures que j'ai appliqué sur mon téléphone (le terme de contre-mesures est un peu fort)
  • Avoir une idée de ce qu'il est possible de faire

Pour répondre au premier point il est tout d'abord nécessaire de vous présenter l'application "Data Defender" développé par la société Authentec.

Cette application permet de protéger l'accès à vos applications critiques (gérant des données privées) comme le mail, l'envoi de SMS, l'accès à votre système de fichiers ou la prise de photo.

droid@screen-6
Vous choisissez quelles sont les applications que vous souhaitez protéger et lorsque que vous souhaitez y accéder vous devez saisir soit un code PIN ou un pattern.

droid@screen-7

C'est bien pratique!

Mais que se passe t'il si l'appel à votre application protégée est fait à partir d'une autre application malveillante!

Pour vérifier le comportement de “Data Defender” j'ai développé une application qui essaie d'envoyer un mail à partir des clients mail présents sur le téléphone. Data Defender est alors appelé et me demande de saisir mon pattern pour accéder au client mail.

On s'en doutait un peu!

Mais, malgré tout, est-il possible d'envoyer un mail à l'insu de l'utilisateur, d'envoyer un SMS sans laisser de trace dans l'historique, d'accéder au système de fichiers, de prendre une photo ou bien d'enregistrer une conversion?

La réponse à toutes ces questions est "oui".

Bien entendu l'application doit avoir les droits d'accès nécessaires pour effectuer toutes ces opérations. Et vous avez remarqué que lorsque vous installez une application la liste des droits d'accès requis par l'application vous est présenté afin de vous prévenir d'une éventuelle menace. Mais qui parmi nous à refusé d'installer une application car elle était un peu trop gourmande en droits d'accès! Pas moi!

A titre d'exemple voici un sous-ensembles des droits d'accès de l'application Facebook.

droid@screen-1

Ca fait peur non?

Dans ce qui suit je vous propose de jeter un oeil au code permettant de réaliser toutes ces opérations. Le code se lit facilement c'est pourquoi je me contente de vous le donner tel quel. Seul la partie de code décrivant la fonction est donnée.

Envoi d'un mail (source: Jon Simon)

Pour réaliser l'envoi d'un mail il est nécessaire de créer un compte Gmail (ne servant qu'à l'envoi du mail) et de renseigner les paramètres correspondants dans le code.

   1: public void sendGMail() {

   2:             Thread thread = new Thread(new Runnable(){

   3:             @Override

   4:             public void run() {

   5:                 try {

   6:                     //Your code goes here

   7:                     Mail m = new Mail("yourfakeaccount@gmail.com", "yourpassword");        

   8:                     String[] toArr = {emailAddress.getText().toString()};       

   9:                     m.setTo(toArr);       

  10:                     m.setFrom("yourfakeaccount@gmail.com");       

  11:                     m.setSubject("Security Demo");       

  12:                     m.setBody(emailText.getText().toString());        

  13:                     m.addAttachment(Environment.getExternalStorageDirectory().getPath() + "/securitydemo.txt");          

  14:                     if(m.send()) {           

  15:                         Toast.makeText(SendEmailDemo.this, "Email was sent successfully.", Toast.LENGTH_LONG).show();         

  16:                     } else {           

  17:                         Toast.makeText(SendEmailDemo.this, "Email was not sent.", Toast.LENGTH_LONG).show();         

  18:                     }       

  19:                 } catch(Exception e) {         

  20:                     Log.e("MailApp", "Could not send email", e);       

  21:                 }    

  22:             }

  23:         });

  24:         thread.start(); 

  25: }

Envoi d'un SMS (source: Gupta.Avinash)




   1: public void sendLongSMS() {         

   2:  

   3:     SmsManager smsManager = SmsManager.getDefault();

   4:     ArrayList<String> parts = smsManager.divideMessage(messageText.getText().toString()); 

   5:     smsManager.sendMultipartTextMessage(phoneNumber.getText().toString(), null, parts, null, null);

   6:     

   7:     Toast.makeText(getApplicationContext(), "Message Sent!", Toast.LENGTH_LONG).show();

   8: }


Accès au système de fichiers (source: Stackoverflow)




   1: public void writeFile() {

   2:     try {

   3:         File sdCard = Environment.getExternalStorageDirectory();

   4:         File dir = new File (sdCard.getAbsolutePath());

   5:         File file= new File(dir, fileName.getText().toString());

   6:         System.out.println(file.getAbsolutePath());

   7:  

   8:         FileOutputStream fOut = new FileOutputStream(file);

   9:  

  10:         OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut);

  11:         myOutWriter.append(fileContent.getText());

  12:         myOutWriter.close();

  13:         fOut.close();  

  14:         Toast.makeText(getApplicationContext(), "File saved!", Toast.LENGTH_LONG).show();

  15:     } catch (IOException e) {

  16:           Toast.makeText(getApplicationContext(), "Failed to write file", Toast.LENGTH_LONG).show();

  17:     }

  18: }


Prise d'une photo (source: Marko Gargenta)

L'intégralité du fichier "java" est fourni pour ceux qui ne sont pas familiers avec l'utilisation des librairies "Graphique".



   1: package com.proxia.securitydemo;

   2:  

   3: import java.io.FileNotFoundException;

   4: import java.io.FileOutputStream;

   5: import java.io.IOException;

   6: import android.content.Context;

   7: import android.graphics.Canvas;

   8: import android.graphics.Color;

   9: import android.graphics.Paint;

  10: import android.hardware.Camera;

  11: import android.hardware.Camera.PreviewCallback;

  12: import android.util.Log;

  13: import android.view.SurfaceHolder;

  14: import android.view.SurfaceView;

  15:  

  16: class Preview extends SurfaceView implements SurfaceHolder.Callback {    

  17:     private static final String TAG = "Preview";    

  18:     

  19:     SurfaceHolder mHolder;    

  20:     public Camera camera;    

  21:     

  22:     Preview(Context context) {        

  23:         super(context);        

  24:         mHolder = getHolder();        

  25:         mHolder.addCallback(this);        

  26:         mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);    

  27:     }    

  28:     

  29:     public void surfaceCreated(SurfaceHolder holder) {        

  30:         camera = Camera.open();        

  31:         try {            

  32:             camera.setPreviewDisplay(holder);            

  33:             camera.setPreviewCallback(new PreviewCallback() {                

  34:                 

  35:                 public void onPreviewFrame(byte[] data, Camera arg1) {                    

  36:                     FileOutputStream outStream = null;                    

  37:                     Preview.this.invalidate();                

  38:                 }            

  39:             });        

  40:         } catch (IOException e) {            

  41:             e.printStackTrace();        

  42:         }    

  43:     }    

  44:     

  45:     public void surfaceDestroyed(SurfaceHolder holder) {        

  46:         camera.stopPreview();        

  47:         camera = null;    

  48:     }    

  49:     

  50:     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {        

  51:         Camera.Parameters parameters = camera.getParameters();        

  52:         parameters.setPreviewSize(w, h);        

  53:         camera.setParameters(parameters);        

  54:         camera.startPreview();    

  55:     }    

  56:     

  57:     @Override    

  58:     public void draw(Canvas canvas) {        

  59:         super.draw(canvas);        

  60:         Paint p = new Paint(Color.RED);        

  61:         Log.d(TAG, "draw");        

  62:         canvas.drawText("PREVIEW", canvas.getWidth() / 2,                

  63:                 canvas.getHeight() / 2, p);    

  64:     }

  65: }

  66:  

Enregistrement d'une conversation (source: Developer Android)

L'intégralité des fichiers "java" est fourni pour ceux qui ne sont pas familiers avec l'utilisation des librairies "Media".



   1: package com.proxia.securitydemo;

   2:  

   3: import android.app.Activity; 

   4: import android.widget.LinearLayout; 

   5: import android.os.Bundle; 

   6: import android.os.Environment; 

   7: import android.view.Menu;

   8: import android.view.MenuInflater;

   9: import android.view.MenuItem;

  10: import android.view.ViewGroup; 

  11: import android.widget.Button; 

  12: import android.view.View; 

  13: import android.view.View.OnClickListener; 

  14: import android.content.Context; 

  15: import android.content.Intent;

  16: import android.util.Log; 

  17: import android.media.MediaRecorder; 

  18: import android.media.MediaPlayer;  

  19: import java.io.IOException;   

  20:  

  21: public class AudioRecordTest extends Activity {     

  22:     private static final String LOG_TAG = "AudioRecordTest";     

  23:     private static String mFileName = null;      

  24:     private RecordButton mRecordButton = null;     

  25:     private MediaRecorder mRecorder = null;      

  26:     private PlayButton   mPlayButton = null;     

  27:     private MediaPlayer   mPlayer = null;    

  28:     

  29:     private void onRecord(boolean start) {         

  30:         if (start) {             

  31:             startRecording();         

  32:         } else {             

  33:             stopRecording();         

  34:         }     

  35:     }      

  36:     

  37:     private void onPlay(boolean start) {         

  38:         if (start) {             

  39:             startPlaying();         

  40:         } else {             

  41:             stopPlaying();         

  42:         }     

  43:     }      

  44:     

  45:     private void startPlaying() {         

  46:         mPlayer = new MediaPlayer();         

  47:         try {             

  48:             mPlayer.setDataSource(mFileName);             

  49:             mPlayer.prepare();             

  50:             mPlayer.start();         

  51:         } catch (IOException e) {             

  52:             Log.e(LOG_TAG, "prepare() failed");         

  53:         }     

  54:     }      

  55:     

  56:     private void stopPlaying() {         

  57:         mPlayer.release();         

  58:         mPlayer = null;     

  59:     }      

  60:     

  61:     private void startRecording() {         

  62:         mRecorder = new MediaRecorder();         

  63:         mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);         

  64:         mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);         

  65:         mRecorder.setOutputFile(mFileName);         

  66:         mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);          

  67:         try {             

  68:             mRecorder.prepare();         

  69:         } catch (IOException e) {             

  70:             Log.e(LOG_TAG, "prepare() failed");         

  71:         }          

  72:         mRecorder.start();     

  73:     }      

  74:     

  75:     private void stopRecording() {        

  76:         mRecorder.stop();         

  77:         mRecorder.release();         

  78:         mRecorder = null;     

  79:     }      

  80:     

  81:     class RecordButton extends Button {         

  82:         boolean mStartRecording = true;          

  83:         OnClickListener clicker = new OnClickListener() {             

  84:             public void onClick(View v) {                 

  85:                 onRecord(mStartRecording);                 

  86:                 if (mStartRecording) {                     

  87:                     setText("Stop recording");                

  88:                 } else {                     

  89:                     setText("Start recording");                 

  90:                 }                 

  91:                 mStartRecording = !mStartRecording;             

  92:             }         

  93:         };          

  94:         

  95:         public RecordButton(Context ctx) {             

  96:             super(ctx);             

  97:             setText("Start recording");             

  98:             setOnClickListener(clicker);         

  99:         }     

 100:     }      

 101:     

 102:     class PlayButton extends Button {         

 103:         boolean mStartPlaying = true;          

 104:         OnClickListener clicker = new OnClickListener() {             

 105:             public void onClick(View v) {                 

 106:                 onPlay(mStartPlaying);                 

 107:                 if (mStartPlaying) {                     

 108:                     setText("Stop playing");                 

 109:                 } else {                     

 110:                     setText("Start playing");                

 111:                 }                 

 112:                 mStartPlaying = !mStartPlaying;             

 113:             }         

 114:         };          

 115:         

 116:         public PlayButton(Context ctx) {             

 117:             super(ctx);             

 118:             setText("Start playing");             

 119:             setOnClickListener(clicker);         

 120:         }     

 121:     }      

 122:     

 123:     public AudioRecordTest() {         

 124:         mFileName = Environment.getExternalStorageDirectory().getAbsolutePath();         

 125:         mFileName += "/audiorecordtest.3gp";     

 126:     }     

 127:     

 128:     @Override     

 129:     public void onCreate(Bundle icicle) {         

 130:         super.onCreate(icicle);          

 131:         LinearLayout ll = new LinearLayout(this);         

 132:         mRecordButton = new RecordButton(this);         

 133:         ll.addView(mRecordButton,             

 134:                 new LinearLayout.LayoutParams(                 

 135:                         ViewGroup.LayoutParams.WRAP_CONTENT,                 

 136:                         ViewGroup.LayoutParams.WRAP_CONTENT,                

 137:                         0));         

 138:         mPlayButton = new PlayButton(this);         

 139:         ll.addView(mPlayButton,             

 140:                 new LinearLayout.LayoutParams(                 

 141:                         ViewGroup.LayoutParams.WRAP_CONTENT,                 

 142:                         ViewGroup.LayoutParams.WRAP_CONTENT,                 

 143:                         0));        

 144:         setContentView(ll);     

 145:     } 

 146:     

 147:     @Override     

 148:     public void onPause() {         

 149:         super.onPause();         

 150:         if (mRecorder != null) {             

 151:             mRecorder.release();             

 152:             mRecorder = null;         

 153:         }          

 154:         if (mPlayer != null) {             

 155:             mPlayer.release();             

 156:             mPlayer = null;         

 157:         }     

 158:     } 

 159:  

 160:     public boolean onPrepareOptionsMenu(Menu menu) 

 161:     {

 162:         super.onPrepareOptionsMenu(menu);

 163:         

 164:         return true;

 165:     }

 166:  

 167:     // ------------------------------------------------------

 168:     // ------------------------------------------------------

 169:     @Override

 170:     public boolean onCreateOptionsMenu(Menu menu) 

 171:     {

 172:         super.onCreateOptionsMenu(menu);

 173:         

 174:         try

 175:         {

 176:             MenuInflater inflater = getMenuInflater();

 177:     

 178:             inflater.inflate(R.menu.securitydemo, menu);

 179:     

 180:             inflater = null;

 181:         }

 182:         catch (Exception e)

 183:         {

 184:         }

 185:  

 186:         return true;

 187:     }

 188:  

 189:     // ------------------------------------------------------

 190:     // ------------------------------------------------------

 191:     @Override

 192:     public boolean onOptionsItemSelected(MenuItem item) 

 193:     {

 194:         switch (item.getItemId()) 

 195:         {

 196:             case R.id.menu_mail: 

 197:             {

 198:                 Intent intent = new Intent(this, SendEmailDemo.class);

 199:                 this.startActivity(intent);

 200:                 finish();

 201:                 return true;

 202:             }

 203:     

 204:             case R.id.menu_sd: 

 205:             {

 206:                 Intent intent = new Intent(this, WriteSDDemo.class);

 207:                 this.startActivity(intent);

 208:                 finish();

 209:                 return true;

 210:             }

 211:     

 212:             case R.id.menu_sms: 

 213:             {

 214:                 Intent intent = new Intent(this, SecurityDemo.class);

 215:                 this.startActivity(intent);

 216:                 finish();

 217:                 return true;

 218:             }

 219:  

 220:             case R.id.menu_picture: 

 221:             {

 222:                 Intent intent = new Intent(this, CameraDemo.class);

 223:                 this.startActivity(intent);

 224:                 finish();

 225:                 return true;

 226:             }

 227:  

 228:             case R.id.menu_audio: 

 229:             {

 230:                 return true;

 231:             }

 232:         }

 233:         

 234:         return false;

 235:     }

 236:  

 237:  

 238: }




   1: package com.proxia.securitydemo;

   2:  

   3: import java.io.FileNotFoundException;

   4: import java.io.FileOutputStream;

   5: import java.io.IOException;

   6: import java.util.ArrayList;

   7:  

   8: import android.app.Activity;

   9: import android.content.Intent;

  10: import android.hardware.Camera;

  11: import android.hardware.Camera.PictureCallback;

  12: import android.hardware.Camera.ShutterCallback;

  13: import android.os.Bundle;import android.os.Environment;

  14: import android.telephony.SmsManager;

  15: import android.util.Log;

  16: import android.view.Menu;

  17: import android.view.MenuInflater;

  18: import android.view.MenuItem;

  19: import android.view.View;

  20: import android.view.View.OnClickListener;

  21: import android.widget.Button;

  22: import android.widget.FrameLayout;

  23: import android.widget.Toast;

  24:  

  25: public class CameraDemo extends Activity {    

  26:     private static final String TAG = "CameraDemo";    

  27:     Camera camera;    

  28:     Preview preview;    

  29:     Button buttonClick;    

  30:     

  31:     /** Called when the activity is first created. */    

  32:     @Override    

  33:     public void onCreate(Bundle savedInstanceState) {

  34:         super.onCreate(savedInstanceState);

  35:         setContentView(R.layout.activity_picturedemo);

  36:         preview = new Preview(this);

  37:         ((FrameLayout) findViewById(R.id.preview)).addView(preview);

  38:         buttonClick = (Button) findViewById(R.id.buttonClick);

  39:         buttonClick.setOnClickListener(new OnClickListener() {            

  40:             public void onClick(View v) {                

  41:                 preview.camera.takePicture(shutterCallback, rawCallback,                        

  42:                         jpegCallback);            

  43:                 }        

  44:             });

  45:         

  46:             Log.d(TAG, "onCreate'd");    

  47:         }

  48:     

  49:         ShutterCallback shutterCallback = new ShutterCallback() {        

  50:             public void onShutter() {            

  51:                 Log.d(TAG, "onShutter'd");        

  52:                 }    

  53:         };    

  54:         

  55:         /** Handles data for raw picture */    

  56:         PictureCallback rawCallback = new PictureCallback() {        

  57:             public void onPictureTaken(byte[] data, Camera camera) {            

  58:                 Log.d(TAG, "onPictureTaken - raw");        

  59:                 }    

  60:         };    

  61:         

  62:         /** Handles data for jpeg picture */    

  63:         PictureCallback jpegCallback = new PictureCallback() {        

  64:             public void onPictureTaken(byte[] data, Camera camera) {            

  65:                 FileOutputStream outStream = null;            

  66:                 try {                

  67:                     outStream = new FileOutputStream(Environment.getExternalStorageDirectory().getPath() + "/stolenpicture.jpg");                

  68:                     outStream.write(data);                

  69:                     outStream.close();                

  70:                     Log.d(TAG, "onPictureTaken - wrote bytes: " + data.length);        

  71:                     

  72:                 } catch (FileNotFoundException e) {                

  73:                     e.printStackTrace();            

  74:                 } catch (IOException e) {                

  75:                     e.printStackTrace();            

  76:                 } finally {        

  77:                 }            

  78:                 Log.d(TAG, "onPictureTaken - jpeg");        

  79:             }    

  80:         };

  81:         

  82:  

  83:         public boolean onPrepareOptionsMenu(Menu menu) 

  84:         {

  85:             super.onPrepareOptionsMenu(menu);

  86:             

  87:             return true;

  88:         }

  89:  

  90:         // ------------------------------------------------------

  91:         // ------------------------------------------------------

  92:         @Override

  93:         public boolean onCreateOptionsMenu(Menu menu) 

  94:         {

  95:             super.onCreateOptionsMenu(menu);

  96:             

  97:             try

  98:             {

  99:                 MenuInflater inflater = getMenuInflater();

 100:                 inflater.inflate(R.menu.securitydemo, menu);        

 101:                 inflater = null;

 102:             }

 103:             catch (Exception e)

 104:             {

 105:             }

 106:  

 107:             return true;

 108:         }

 109:  

 110:         // ------------------------------------------------------

 111:         // ------------------------------------------------------

 112:         @Override

 113:         public boolean onOptionsItemSelected(MenuItem item) 

 114:         {

 115:             switch (item.getItemId()) 

 116:             {

 117:                 case R.id.menu_mail: 

 118:                 {

 119:                     Intent intent = new Intent(this, SendEmailDemo.class);

 120:                     this.startActivity(intent);

 121:                     finish();

 122:                     return true;

 123:                 }

 124:         

 125:                 case R.id.menu_sd: 

 126:                 {

 127:                     Intent intent = new Intent(this, WriteSDDemo.class);

 128:                     this.startActivity(intent);

 129:                     finish();

 130:                     return true;

 131:                 }

 132:         

 133:                 case R.id.menu_sms: 

 134:                 {

 135:                     Intent intent = new Intent(this, SecurityDemo.class);

 136:                     this.startActivity(intent);

 137:                     finish();

 138:                     return true;

 139:                 }

 140:  

 141:                 case R.id.menu_picture: 

 142:                 {

 143:                     return true;

 144:                 }

 145:  

 146:                 case R.id.menu_audio: 

 147:                 {

 148:                     Intent intent = new Intent(this, AudioRecordTest.class);

 149:                     this.startActivity(intent);

 150:                     finish();

 151:                     return true;

 152:                 }

 153:             }

 154:             

 155:             return false;

 156:         }

 157:  

 158: }

 159:  

 160:  


Bien sûr toutes ces fonctions peuvent être combinées, comme par exemple l'enregistrement d'une conversation, le stockage du fichier "audio" dans le système de fichiers et l"envoi de ce fichier par mail.

L'application SecurityDemo est téléchargeable ICI.

Voici quelques captures d'écrans:

droid@screen-21

droid@screen-22

N'hésitez pas à me contacter si vous souhaitez avoir plus d'informations sur cette application.

Partager avec...