Andriod APP逆向——网络请求
Android网络请求常用模块
常见的Web HTTP请求分为GET和POST请求,在python中使用的是urllib,requests模块,在Android里用的是okhttp,retrofit模块,其中okhttp和retrofit的关系就和urllib和requests的关系一样,后者均是在前者的基础上做了二次封装,使其使用起来更加方便。
Web HTTP请求的数据包可以分为两种形式,form表单和json字符串,在python中这两种形式是通过requests方法的参数来区分的,但在Android中的okhttp也会有相应提交两种形式的方法。
当接收到Web数据包时,接收到的如果时json字符串的话,就需要对数据进行反序列化,python中requests模块帮我们完成了这个工作,但在Android中需要引入Gson模块来实现反序列化。
在浏览器Web应用中,使用cookies文件保存用户的信息,在Android里使用xml文件实现类似的功能,因此还需要学习下Android操作xml文件的方法。
OKHTTP
使用okhttp之前要做如下配置:
引入,在build.gradle中
implementation “com.squareup.okhttp3:okhttp:4.9.1”
配置,在AndroidManifest.xml中,application标签同级上方添加
只能发送https请求。
配置,在AndroidManifest.xml中,application标签内部添加 android:networkSecurityConfig=“@xml/network_security_config”
在res目录下新建一个xml文件夹,里面新建一个network_security_config.xml文件,写入以下内容:
创建 GET 请求 ---> [创建线程]--> 否则报错
String username = String.valueOf(txtUser.getText());
String password = String.valueOf(txtPwd.getText());HashMap dataMap = new HashMap();
dataMap.put("username", username);
dataMap.put("password", password);
// 1. 将用户名和密码 发送到后台 (第三方 OKHttp)
// 1.1 引入依赖 bulid.gradle 文件中 引入 implementation "com.squareup.okhttp3:okhttp:4.9.1"
// 1.2 在Android中,默认不允许发送网络请求; 在AndroidManifest.xml 中 配置
// 1.3 调用 OKHttp包去发送请求。// 1.3.1 创建 GET 请求 ---> [创建线程]--> 否则报错
// 创建线程并执行 run 方法
new Thread(){@Overridepublic void run(){OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url("https://api.luffycity.com/api/v1/course/actual/?category_id=1").build();Call call = client.newCall(request);try {Response response = call.execute(); // 发送请求ResponseBody body = response.body();String resString = body.string();Log.i("登录界面--->", resString);// 获取数据并处理} catch (IOException e) {e.printStackTrace();}}}.start();
创建 POST 请求 ---> [创建线程]
form表单格式
数据包以表单格式提交,例如:
username=xxxx&password=123
new Thread(){@Overridepublic void run(){OkHttpClient client = new OkHttpClient();// 构建请求体FormBody form = new FormBody.Builder().add("username", "xxxx").add("password", "123").build();// form ---> post 请求体Request request = new Request.Builder().url("https://api.luffycity.com/api/v1/auth/password/login/?loginWay=password").post(form).build();Call call = client.newCall(request);try {Response response = call.execute(); // 发送请求ResponseBody body = response.body();String resString = body.string();Log.i("登录界面--->", resString);// 获取数据并处理} catch (IOException e) {e.printStackTrace();}}}.start();
json格式
数据包以json格式提交,例如:
{username:"xxxx",password:123,
}
new Thread() {@Overridepublic void run() {OkHttpClient client = new OkHttpClient();// 构建请求体// FormBody form = new FormBody.Builder().add("username", "xxxx").add("password", "123").build();//JSONObject json = new JSONObject();//json.put("username", "dk");//json.put("password", "123");JSONObject json = new JSONObject(dataMap);String jsonString = json.toString();RequestBody form = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), toString());// form ---> post 请求体Request request = new Request.Builder().url("https://api.luffycity.com/api/v1/auth/password/login/?loginWay=password").post(form).build();Call call = client.newCall(request);try {Response response = call.execute(); // 发送请求ResponseBody body = response.body();String resString = body.string();// Json反序列化,字符串转换成对象.// json.load(json字符串) --> python// Android 需要 GSON --> 引入 (implementation "com.google.code.gson:gson:2.8.6")//String responseString = "{\"token\": \"dsajkdhjksald\", \"url\": \"https://www.httpbin.org/post/\",\"dataList\":[{\"id\": 1, \"name\": \"dk\"},{\"id\": 2, \"name\": \"dkk\"}]}";HttpResponse res = new Gson().fromJson(responseString, HttpResponse.class);Log.e("登录界面--->", res.toString()); //HttpResponse{url='https://www.httpbin.org/post/', origin='101.248.149.62', dataList=[Item{id=1, name='dk'}, Item{id=2, name='dkk'}]}// 保存起来:cookies, localstorage// andorid -> xml 文件 -> data/data/com.example.myapplication/shared_prefsSharedPreferences sharedPreferences = getSharedPreferences("sp_MyApplication", MODE_PRIVATE);SharedPreferences.Editor editor= sharedPreferences.edit();editor.putString("token", res.token);editor.commit();// 跳转首页Intent in = new Intent(mcontext, IndexActivity.class);startActivity(in);} catch (IOException e) {e.printStackTrace();}}}.start();
两者区别在于:
form表单格式使用的是FormBody构造请求体
json字符串格式使用JSONObject和RequestBody两者一起构造请求体
请求拦截器
有一些请求头信息是每一个HTTP请求都会携带的,这些公共的请求头信息在python的scrapy爬虫框架中,用中间件来实现,在Android App的okhttp中,用请求拦截器来实现,下面是拦截器简单的测试代码。
// 4. 发送请求拦截器
Interceptor interceptor = new Interceptor() {@NonNull@Overridepublic Response intercept(@NonNull Chain chain) throws IOException {String sign = "sfsadsadsa";// 请求还未发送,在请求体中增加了一个请求头Request request = chain.request().newBuilder().addHeader("x-gorgon", sign).build();Response response = chain.proceed(request);return response;}
};
new Thread() {@Overridepublic void run() {// 加入拦截器OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();// 构建请求体// FormBody form = new FormBody.Builder().add("username", "xxxx").add("password", "123").build();//JSONObject json = new JSONObject();//json.put("username", "dk");//json.put("password", "123");JSONObject json = new JSONObject(dataMap);String jsonString = json.toString();RequestBody form = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), toString());// form ---> post 请求体Request request = new Request.Builder().url("https://api.luffycity.com/api/v1/auth/password/login/?loginWay=password").post(form).build();Call call = client.newCall(request);try {Response response = call.execute(); // 发送请求ResponseBody body = response.body();String resString = body.string();// Json反序列化,字符串转换成对象.// json.load(json字符串) --> python// Android 需要 GSON --> 引入 (implementation "com.google.code.gson:gson:2.8.6")//String responseString = "{\"token\": \"dsajkdhjksald\", \"url\": \"https://www.httpbin.org/post/\",\"dataList\":[{\"id\": 1, \"name\": \"dk\"},{\"id\": 2, \"name\": \"dkk\"}]}";HttpResponse res = new Gson().fromJson(responseString, HttpResponse.class);Log.e("登录界面--->", res.toString()); //HttpResponse{url='https://www.httpbin.org/post/', origin='101.248.149.62', dataList=[Item{id=1, name='dk'}, Item{id=2, name='dkk'}]}// 保存起来:cookies, localstorage// andorid -> xml 文件 -> data/data/com.example.myapplication/shared_prefsSharedPreferences sharedPreferences = getSharedPreferences("sp_MyApplication", MODE_PRIVATE);SharedPreferences.Editor editor= sharedPreferences.edit();editor.putString("token", res.token);editor.commit();// 跳转首页Intent in = new Intent(mcontext, IndexActivity.class);startActivity(in);} catch (IOException e) {e.printStackTrace();}}}.start();
和前面没有拦截器的区别:
在创建okhttp前先创建了拦截器
在创建okhttp时,添加拦截器
NO_PROXY
一些App为了防止抓包,会使用okhttp的NO_PROXY参数来禁止Android手机设置系统代理,遇到这类情况可以使用Drony App进行无代理抓包,也可以利用小黄鸟等App进行本地VPN带抓包。
关于无代理抓包可以参考:Python爬虫之对app无代理模式下的抓包分析,以及针对这种的反爬优化方案 - Eeyhan - 博客园 (cnblogs.com)
retrofit
使用retrofit之前要做如下配置:
引入,在build.gradle中 implementation “com.squareup.retrofit2:retrofit:2.9.0”
具体使用方法如下:
写接口,声明网络请求
package com.example.liyang;package com.example.liyang;import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import retrofit2.http.GET;
import retrofit2.http.Query;public interface HttpReq {// 向/api/v1/post 发送POST请求,表单格式 name=xx&pwd=xxx@POST("/api/v1/post")@FormUrlEncodedCall postLogin(@Field("name") String userName, @Field("pwd") String password);// 向/api/v2/xxx 发送GET请求,表单格式 ?age=xxx@GET("/api/v2/xxx")Call getInfo(@Query("age") String age);// 向/post/users 发送POST请求 json字符串格式 {name:xxxx,age:123}@POST("/post/users")Call postLoginJson(@Body RequestBody body);
}
调用接口,给接口传参,发送请求
new Thread() {@Overridepublic void run() {Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.31.201:9999/").build();HttpReq httpRequest = retrofit.create(HttpReq.class); // http://192.168.31.201:9999/api/v1/post // name=xx&pwd=xxxCall call = httpRequest.postLogin("wupeiqi", "666");try {ResponseBody responseBody = call.execute().body();String responseString = responseBody.string();Log.i("登录", responseString);} catch (Exception e) {e.printStackTrace();}}
}.start();
new Thread() {@Overridepublic void run() {// http://192.168.31.201:9999/api/v2/xxx?age=123Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.31.201:9999/").build();HttpReq req = retrofit.create(HttpReq.class);Call call = req.getInfo("123");try {ResponseBody responseBody = call.execute().body();String responseString = responseBody.string();Log.e("Retrofit返回的结果", responseString);} catch (Exception e) {e.printStackTrace();}}
}.start();
new Thread() {@Overridepublic void run() {Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.31.201:9999/").build();HttpReq httpRequest = retrofit.create(HttpReq.class);JSONObject json = new JSONObject(dataMap);String jsonString = json.toString();RequestBody form = RequestBody.create(MediaType.parse("application/json;charset=utf-8"),jsonString);// http://192.168.31.201:9999/post/users // {username:"root",password:"123456","sign":"xxxxdfsdfsdfsdfdfd"}Call call = httpRequest.postLoginJson(form);try {ResponseBody responseBody = call.execute().body();String responseString = responseBody.string();Log.i("登录", responseString);} catch (Exception e) {e.printStackTrace();}}
}.start();
注意事项:
retrofit是在okhttp上的进一步封装,特点是可以把请求的url分成几段,分别在不同的地方来指定,可以更灵活地配置url
当遇到了用retrofit写的接口时,不再是去找实现它的类,而是去找调用它的类,拼接出请求的url和找到传递的参数
代码比较冗长,重点关注定义接口的方式,决定了请求的样式,以及调用接口的方式,决定了传递的参数,两部分合起来决定请求的url
GSON
类似python中的json模块,用于序列化对象和反序列化json字符串的,使用Gson前需要引入
implementation ‘com.google.code.gson:gson:2.8.6’
序列化,对象 -> 字符串类型
class HttpContext{public int code;public String message;public HttpContext(int code,String msg){this.code = code;this.message = msg;}
}
HttpContext obj = new HttpContext(1000,"成功");
String dataString = new Gson().toJson(obj); // '{"code":1000,"Message":"成功"}'
反序列化,字符串 -> 对象
// JSON格式
String dataString = "{\"status\": true, \"token\": \"fffk91234ksd\", \"name\": \"武沛齐\"}";
class HttpResponse{public boolean status;public String token;public String name;
}
HttpResponse obj = new Gson().fromJson(dataString,HttpResponse.class);
// obj.status obj.name obj.token
如果反序列化时,存在字典嵌套
String responseString = "{\"origin\": \"110.248.149.62\",\"url\": \"https://www.httpbin.org/post\",\"dataList\":[{\"id\":1,\"name\":\"一个小黑\"},{\"id\":2,\"name\":\"eric\"}]}";
class Item {public int id;public String name;
}
public class HttpResponse {public String url;public String origin;public ArrayList- dataList;
}
HttpResponse obj = new Gson().fromJson(dataString, HttpResponse.class);
// obj.url obj.origin
Item objItem = obj.dataList.get(1);
// objItem.name
保存到XML文件
保存到手机上的位置:
/data/data/com.example.liyang/shared_prefs/sp_city.xml
保存 token
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("token","111111");
editor.commit();
删除 token
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.remove("token");
editor.commit();
读取 token
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE);
String token = sp.getString("token","");
记住SharedPreferences对象是用来读写手机App本地的XML文件的
XML文件和Cookies很像,要么是本地算法生成的,要么是HTTP请求服务器返回的。
总结
jadx、jeb去反编译安卓代码:
关键字搜索
根据请求的流程逐步排查
java调用
注意:小app了解上面内容就能完全将其逆向出来。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!