声明:本文仅供学习使用,如有侵犯,请通知我,24小时内删除!
前言:反编译工作特点:1.体力活 2.需要耐心 3.坚持不懈 4.熟能生巧
纲目
- 反编译作用
- 反编译工具
- Smali简介
- 代码插桩
- 实战例子(如何获取网易云音乐接口,查看整个反编译流程)
反编译作用
对于公司而言
- 了解行业内对手特点功能的实现原理。
- 修改应用,添加作弊插件(常见于游戏公司)。
对于个人而言
- 集成广告二次打包发布市场,挣广告费(最常见)。
- 修改系统应用,添加功能,制作特色刷机包。
- 单纯为了学习(我)。
反编译工具
主要工具: Apktool,dex2jar,JD-GUI
额外工具: ida,HexEdit
功能:将Apk反编译得到资源文件及smali
代码,将Jar反编译得到smali
代码,以及回编译。其中将dex
文件反编译成smali
的工具为baksmali.jar
,回编译smali
成dex
的为smali.jar
常用命令
1 2 3 4 5 6 7 8 9
| # 反编译 apktool d xxx.apk # 回编译 apktool b xxx # 参数 # -r,--no-res 忽略资源文件 # -s,--no-src 忽略代码文件
|
dex2jar
将dex文件反编译成*.class
集合的jar文件,之后可以使用JD-GUI
工具查看
常用命令: sh d2j-dex2jar.sh classes.dex
JD-GUI
用于查看*.class
集合的jar文件,如第三方sdk的jar包,或者dex2jar转换得到的jar文件
Smali简介
摘自http://blog.csdn.net/wdaming1986/article/details/8299996
Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;
Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)
原始类型
标记 |
类型 |
V |
void, 只用于返回值 |
Z |
boolean |
B |
byte |
S |
short |
C |
char |
I |
int |
J |
long(64bits) |
F |
float |
D |
double(64bits) |
对象类型
Lpackage/className;
- L:表示这是一个对象类型
- package:该对象所在的包名
- className: 类的simpleName
数组的表示形式:
[I :表示一个整形的一维数组,相当于java的int[];
对于多维数组,只要增加[ 就行了,[[I = int[][];注:每一维最多255个;
对象数组的表示形式:
[Ljava/lang/String 表示一个String的对象数组;
方法的表示形式:
Lpackage/className;—>methodName(III)Z 详解如下:
Lpackage/className 表示类型
methodName 表示方法名
III 表示参数(这里表示为3个整型参数)
说明:方法的参数是一个接一个的,中间没有隔开;
字段的表示形式:
Lpackage/className;—>fieldName:Ljava/lang/String;
即表示: 包名,字段名和各字段类型
有两种方式指定一个方法中有多少寄存器是可用的:
.registers 指令指定了方法中寄存器的总数
.locals 指令表明了方法中非参寄存器的总数,出现在方法中的第一行
举例:
java源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.decompile; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); log("使用私有对象方法,p0是MainActivity.this这个对象实例,p1是第一个参数"); sLog("使用公有静态方法,p0是第一个参数"); } private void log(Object o) { Log.e("private object method", String.valueOf(o)); } public static void sLog(Object o) { Log.e("public static method", String.valueOf(o)); } }
|
smali代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| .method private log(Ljava/lang/Object;)V .locals 2 .param p1, "o" .prologue const-string v0, "private object method" invoke-static {p1}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String; move-result-object v1 invoke-static {v0, v1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I return-void .end method .method public static sLog(Ljava/lang/Object;)V .locals 2 .param p0, "o" .prologue const-string v0, "public static method" invoke-static {p0}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String; move-result-object v1 invoke-static {v0, v1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I return-void .end method .method protected onCreate(Landroid/os/Bundle;)V .locals 1 .param p1, "savedInstanceState" .prologue invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V const v0, 0x7f04001b invoke-virtual {p0, v0}, Lcom/decompile/MainActivity;->setContentView(I)V const-string v0, "\u4f7f\u7528\u79c1\u6709\u5bf9\u8c61\u65b9\u6cd5\uff0cp0\u662fMainActivity.this\u8fd9\u4e2a\u5bf9\u8c61\u5b9e\u4f8b\uff0cp1\u662f\u7b2c\u4e00\u4e2a\u53c2\u6570" invoke-direct {p0, v0}, Lcom/decompile/MainActivity;->log(Ljava/lang/Object;)V const-string v0, "\u4f7f\u7528\u516c\u6709\u9759\u6001\u65b9\u6cd5\uff0cp0\u662f\u7b2c\u4e00\u4e2a\u53c2\u6570" invoke-static {v0}, Lcom/decompile/MainActivity;->sLog(Ljava/lang/Object;)V return-void .end method
|
对象方法调用:
1
| (invoke-direct|invoke-virtual) {xx, ...params}, Lpackage/className;->methodName(...paramTypes)returnType
|
invoke-direct除了用于对象私有方法,还可以用于对象的初始化
invoke-virtual用于对象公有方法
xx:Lpackage/className的实例
params:为参数列表
paramType:参数列表对应类型
returnType:返回值类型
静态方法调用:
1
| invoke-static {...params}, Lpackage/className;->methodName(...paramTypes)returnType
|
invok-static用于调用静态方法
静态方法与对象方法,区别是参数列表第一个参数不是Lpackage/className的实例对象
代码插桩
经过上述java和smali代码对照,再查看一下smali语法,由于我们没有java源码,无法调试,只能静态分析代码,然后通过一些log进行跟踪分析别人的代码逻辑,很容易联想到代码插桩。通过代码插桩,我们可以修改第三方应用,实现某些功能。目前市面上基本上通过xposed来实现自动抢红包,实际上,也可以通过反编译修改微信应用达到效果,之前实现过,但不想维护,没玩下去。
代码插桩,需要讲究技巧。在Android Studio和Eclipe中编写好自己的逻辑代码,让其生成apk,然后同时反编译第三方应用和自己的逻辑代码应用,然后对代码进行合并。
如之前弄的微信红包(由于之前没考虑6.0系统运行时权限问题,后面取消了这个微信红包分享文章)。
由上图可知,我所谓的技巧有
- 伪造所依赖的类和字段,标上
@Deprecated
注解,表示这部分代码无需进行插桩,只是为了Android Studio能正常编译,生成插桩代码应用
- 在目标类,编写相关逻辑代码,当然方法名必须清晰,避免以后找不回来
- 反编译目标应用和插桩代码应用,进行smali代码合并
- 然后在目标类中的关联方法中调用我们自己写的逻辑方法,达到效果
实战例子
因为很久之前就反编译获取了网易云音乐接口,例子应用:neteasecloudmusic_v3.3.2.apk
抓包
使用手机端PacketCapture或者电脑端Charles进行抓包,查看网络请求详情,注意,不一定能抓到包,有些应用做了仿代理访问,抓不了包的。
这个请求为点击启动页面的立即体验按钮发出,从图可知,请求参数只有params
这个加密表单数据,关键字为params
,另外请求头Cookie
看上去需要组装deviceId
等参数。根据最后反编译结果,这里返回结果中的Set-Cookie
中的cookie字段是用于后续请求的。即这个请求在启动时必须发起,后续请求都需要MUSIC_A
和__csrf
这两个cookie
反编译
使用Apktool将neteasecloudmusic_v3.3.2.apk反编译成资源文件和smali代码文件
apktool d neteasecloudmusic_v3.3.2.apk
1 2 3 4 5 6 7 8 9 10 11 12 13
| ➜ org tree -L 1 . ├── AndroidManifest.xml ├── apktool.yml ├── assets ├── lib ├── original ├── res ├── smali ├── smali_classes2 └── unknown 7 directories, 2 files
|
使用dex2jar将neteasecloudmusic_v3.3.2.apk的dex文件转成jar文件,使用jd-gui查看
sh dex2jar.sh classes.dex
sh dex2jar.sh classes2.dex
清理障碍
刚才我们知道params
是请求的关键字,那么使用sublime text
编辑器搜索"params"
,你会发现搜索不到任何结果,上一张图jd-gui的图System.loadLibrary
方法,会调用a.c
方法处理字符串。那意思就是网易云音乐将所有字符串都加密了,通过a.auu.a.c
方法进行解密。
打开a.auu.a
这个类,我们随便拖拖界面,发现有bad base64
,那么可以猜到这个字符串加密为base64
加密,当然也有它的一些逻辑。
使用jd-gui能够将dex2jar生成的jar文件,保存成可阅读的java文件,那么导出。接着编写java程序(随便写写,达到目的即可),将所有机密字符串解密。
根据smali语法看混淆代码静态分析,最终解密字符串代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| public static String decode(String str) { byte[] bytes = Base64.decode(str, Base64.DEFAULT); return new String(xor(bytes)); } private static byte[] xor(byte[] source) { int len = source.length; int len2 = "Encrypt".length(); for (int i = 0, j = 0; i < len; i++, j++) { j %= len2; source[i] = (byte) (source[i] ^ "Encrypt".charAt(j)); } return source; } public static String encode(String str) { return Base64.encodeToString(xor(str.getBytes()), Base64.DEFAULT); } private static String key = "a.auu.a.c"; private static String handleLine(String source){ try{ while(source.indexOf(key)!=-1){ int index = source.indexOf(key); String end = source.substring(index); String gone = source.substring(index, index+end.indexOf(")")+1); String encrypt = gone.substring(gone.indexOf("\"")+1, gone.lastIndexOf("\"")); source = source.replace(gone, "\""+decode(encrypt)+"\""); } while(source.indexOf("a.c(\"")!=-1){ int index = source.indexOf("a.c(\""); String end = source.substring(index); String gone = source.substring(index, index+end.indexOf(")")+1); String encrypt = gone.substring(gone.indexOf("\"")+1, gone.lastIndexOf("\"")); source = source.replace(gone, "\""+decode(encrypt)+"\""); } }catch(Exception e){ } return source; } private static void process(File file){ File[] files = file.listFiles(); for(int i=0;i<files.length;i++){ File f = files[i]; if(f.isDirectory()){ process(f); }else{ handleFile(f); } } } private static void handleFile(File source){ String path = source.getPath(); File file = new File(path+"bak"); source.renameTo(file); try{ InputStream is = new FileInputStream(file); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; File dest = new File(path); OutputStream os = new FileOutputStream(dest); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os)); while((line=br.readLine())!=null){ bw.write(handleLine(line)); bw.write("\n"); } bw.close(); br.close(); }catch(IOException e){ System.out.println(e.getMessage()); } file.delete(); } public static void main(String[] args){ process(new File("保存的java文件路径")); }
|
再次使用sublime text
搜索"params"
字段,你会发现有结果,如下:
从图,我们看到了曙光,因为只要在这个地方通过代码插桩,就能把加密前整个请求参数列表全部打印出来,然后分析每个参数的来源。
插桩代码得到的log(这是提前揭晓的结果,中间还有障碍)
1 2 3
| 05-03 14:45:58.556 2137-2185/? E/params: {"\/api\/cdns":"","\/api\/v1\/vehiclefm\/visible":"","\/api\/log\/mam\/freq\/get":"","\/api\/android\/version":"{\"carrier\":\"unicom\",\"ismiui\":false}","\/api\/copyright\/pay_fee_message\/config":"","header":{"MUSIC_A":"47e30a9b79cacd30979d5c02186a8611851676624cde1c280b151856c9fd0d5c7c829645fe34e3c88d2e96001ea988b7446253d594d49cc2093469c45a9b7fe670500f1ae8f45856590694c120e20fee05dbee31830798be75c7cd93f1d9b7ac87577e38f566c25a80bec5f4143606251f977bdb2e8a00e70493e93303df938bbf122d59fa1ed6a2","__csrf":"0f2529b2f4466890e77e1a176a39ba7d","deviceId":"MzUyODI0MDYwNzk1OTU5CTAwOmVlOmJkOjQ0OmI3OmQ2CThjM2JhNGY3MjNmNjlhM2YJSEM0NlNXWTAxMjI0","appver":"3.3.2","os":"android","osver":"5.0.2","mobilename":"HTCM8Sw","resolution":"1776x1080","channel":"google","requestId":"1493793958567_865"}} 05-03 14:45:58.556 2137-2185/? E/params: /api/batch 05-03 14:45:58.566 2137-2185/? E/params: C561B86F974CBA39FB9AB3222AC3E77103E75A6A409D4B3CC17C4A7671C9FAD03F74A144F4E75C97B372C380BC2A6329450A2E19AC4BA84F673435A636D90439DD4D5E0E685EC201677DF834E5CD93103AFFEEA66517CD5A6593811E02F1A4BF3257B48D3EBBA5B7B7792621CB9A484E5C39FE31174DD5284BDAE946FC9D308912E015DA16629D64D53F3EE0236203C0B1690A2AE6BDC47B8BB040C746DD8AB746E2AA107B1AE8C4BC452B9C82470F6C771CAF0396B00AD43C2884E25DB41A49FEF1ED8294EDC325C712EC63145D211BFF041BF51003503DEA806F0AF6039F2FB4E2098D15592353D76485D102A81D5CA3304EFFF3044027E2855539BAA021073D049A4F894E560E4E95BCDC7E7E98988599215CD80D429C9EEAB3CE7C1B2D9034CB31BE681634B95201F0E181125F1ED5FECFC6D5B2D07CA8B98324994248E9403A5F7A65B34EEE8BE8B6E0316B943DD3B62BFEB042C119BC9C86896CCB6F70306FE1646792E2E4769E8DD453078DBEABD7285E77B6B41F6CF8217ACB795CA0214CBE467637D253A97A30F1D91BCCD13D9EEC5611018645B3F4BEC780FE4EA0B11209EC9CC951504A083DAA6877CD216C815EE33CC33EE0D0F10EF02B3F3C630DBDFB365D6FF61C6EEA398C13BAF5BAA6CE0E34786855FD85DE360A227D0F52FED554FB63B8044E6E21E494E92CAFAA63DB0FB4C5201DB5646F2A03D40AEEA031452D4D0B72B1775DC7BFE04EBD8B1E78E7067B31569460F1CA15890998592E2B0B18A540D3D46F671B82E601688C79A3D30CF201A89520E030E5823B568CDC1DF69493E31B49AA82DA5A1C5F5279BF8425F96A5DE841EEC729B2C81DD3C3E70BE4F336A685E96CE3B47CA3A4AA9E8ACABBCD2D040153E20C6EEA33D7E1ECBE7850B450C1A3DC60C66BD777A3ED4823023EEB3E3342542DDCF4743D12D9086544D78643FEDFE6D5C80666652F90CE073F293CBF179F6046A82C307EDD9B462E468A2F7D57A9BB8EFB173B08E1CB19AE98FB44334BECEA542C07CC5B8E977066262269CBD13E2B457EF60FDC6AA5560152569686C9FE8CA57387CF42E422D3AC2F5C4BD9CE1F2A3D1FD72269DCF4DCA3C200917A3C83B848CFDCFE97274BE7E1DBE65AF9FE0A466F2B218E6E65C50BBFDD2C8A19858AFFB401A0FC9B3942BC19B8869F796BA28AA09D98F64B35953970E25FB4705EC886D68861A2FECAA9AE64
|
第一行为加密前的参数
第二行为请求的接口路劲
第三行为加密后的params字段值
破解签名
上述说到中间还有障碍,指的是很多第三方apk往往会在so校验应用的签名,或者用应用的签名作为请求参数加密的一些参数,加大接口被调用的难度。当代码插桩添加log后,使用apktool回编,然后签名,你会发现应用打开就crash。因此必须要解决签名问题。
继续分析,从上一张图,可以看出,加密参数调用的是NeteaseMusicUtils.serialdata
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| static { UtilInterface.load("poison"); nativeInit(NeteaseMusicApplication.a()); } public class UtilInterface { static { System.loadLibrary("neutil"); } public static native void l(String paramString); public static void load(String paramString) { l(paramString); } } armeabi ├── libFMProcessor.so ├── libMP3Encoder.so ├── libbitmaps.so ├── libblur.so ├── libfpGenerate.so ├── libgifimage.so ├── libijkffmpeg.so ├── libijkplayer.so ├── libijksdl.so ├── libimagepipeline.so ├── liblocSDK3.so ├── libmemchunk.so ├── libndkbitmap.so ├── libneutil.so ├── libwakeup.so ├── libwebp.so └── libwebpimage.so
|
从代码看来,貌似加载了个libneutil.so
,然后通过libpoison.so
文件,只有libneutil.so
文件,并没有libpoison.so
这个文件。
通过使用ida调试,目前貌似只能在windows机器上面调试so文件,教程:使用IDA Pro动态调试so文件
另外libneutil.so
做了反调试处理,需要跳过反调试,教程:在JNI_Onload 函数处下断点避开针对IDA Pro的反调试
在调试过程中,你会发现libneutil.so
会使用dlopen
和dlsym
的方法加载了另外的libpoison.so
。
生成目录为:/data/data/com.netease.cloudmusic/.cache/libpoision${pid}.so
如果你要提取这个文件,需要是root手机权限。当然我手机root过,直接拷贝出来,后面用于加密参数。
以上还没有说到如何破解签名,因为破解签名是在libpoison.so里面,libneutil.so仅仅是加了反调试和解密assets
目录下的data.db
生成libpoison.so
如何破解libpoison.so
里面的签名验证?
因为获取签名只有java方法context.getPackageManager().getPackageInfo().signatures
,因此,肯定会从java层传递一个context
对象到jni,jni又只能反射调用java方法,字符串的定义,在so也是一目了然,可以使用HexEdit工具修改的。因此,我们可以欺骗so,让他获取正确的应用签名,而不是二次打包应用后的签名。
由于libpoison.so还是用了MessageDigest.getInstance("MD5").update(signature)
这个静态方法对应用签名进行md5校验,因此,直接修改libpoison.so
里面的MessageDigest
类引用为自己的java类com.netease.cloudmusic.Hack
,在com.netease.cloudmusic.Hack
里面给它正确结果即可,类名长度和MessageDigest长度一样,并保证被so反射调用到的方法都补全。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package com.netease.cloudmusic; /** * Created by 4ndroidev on 16/7/31. * cheat the poison.so to load signature from here rather than apk */ public class Hack { private static Hack instance = new Hack(); private Hack() { } /** * how to get netease.signature? * <code> * PackageManager packageManager = getPackageManager(); * PackageInfo info = packageManager.getPackageInfo("com.netease.cloudmusic", PackageManager.GET_SIGNATURES); * Signature[] signatures = info.signatures; * FileOutStream os = new FileOutputStream(new File('netease.signature')); * os.write(signatures[0].toByteArray()); * os.close(); * </code> */ public static Hack getInstance(String useless) { return instance; } //just for native call public void update(byte[] bytes) { } /** * directly return the result of md5(signature); actually you should follow the steps * 1. covert assets/netease.signature to byte array variable `signature` * 2. * <code> * MessageDigest md = MessageDigest.getInstance("MD5").update(signature) * md.update(signature); * byte[] digest = md.digest(); * </code> */ public byte[] digest() { byte[] digest = {-38, 107, 6, -99, -95, -30, -104, 45, -77, -29, -122, 35, 63, 104, -41, 109}; return digest; // hardcode: digest = md5(signature); } }
|
也可以用另外的破解签名方法,可参照另外一篇分享:Android Apk 签名破解
回编译
由于我们获取了libpoison.so
,以及破解了libpoison.so
的签名验证,即可使用System.loadLibrary("poison");
代替UtilInterface.load("poison");
,避免回编重签导致奔溃问题。
回编译,由于插桩log,我们便得一个个请求,同理,可以找出返回结果,添加log。
静态分析
虽然打出了每个请求参数的log,事实还有很多工作,例如参数的来源,这些需要看混淆代码获得,至于怎么快速定位,那就是通过完整的类名+方法名,或者类名+属性名进行搜索。
只要熟悉smali,是可以直接将smali逆回java,体力活,只要耐心即可,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
| // smali .method private static a(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;)Ljava/util/Map; .locals 7 .annotation system Ldalvik/annotation/Signature; value = { "(", "Ljava/lang/String;", "Ljava/lang/String;", "Ljava/util/Map", "<", "Ljava/lang/String;", "Ljava/lang/String;", ">;", "Ljava/lang/String;", ")", "Ljava/util/Map", "<", "Ljava/lang/String;", "Ljava/lang/String;", ">;" } .end annotation .prologue const/4 v0, 0x0 if-nez p2, :cond_0 new-instance v1, Ljava/util/HashMap; invoke-direct {v1}, Ljava/util/HashMap;-><init>()V :goto_0 if-eqz p0, :cond_1 :try_start_0 const-string v2, "Yw==" invoke-static/range {v2 .. v2}, La/auu/a;->c(Ljava/lang/String;)Ljava/lang/String; move-result-object v2 invoke-virtual {p0, v2}, Ljava/lang/String;->split(Ljava/lang/String;)[Ljava/lang/String; move-result-object v2 array-length v3, v2 :goto_1 if-ge v0, v3, :cond_1 aget-object v4, v2, v0 const-string v5, "eA==" invoke-static/range {v5 .. v5}, La/auu/a;->c(Ljava/lang/String;)Ljava/lang/String; move-result-object v5 invoke-virtual {v4, v5}, Ljava/lang/String;->split(Ljava/lang/String;)[Ljava/lang/String; move-result-object v4 const/4 v5, 0x0 aget-object v5, v4, v5 const/4 v6, 0x1 aget-object v4, v4, v6 invoke-interface {v1, v5, v4}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; :try_end_0 .catch Lorg/json/JSONException; {:try_start_0 .. :try_end_0} :catch_0 add-int/lit8 v0, v0, 0x1 goto :goto_1 :cond_0 new-instance v1, Ljava/util/HashMap; invoke-direct {v1, p2}, Ljava/util/HashMap;-><init>(Ljava/util/Map;)V goto :goto_0 :cond_1 :try_start_1 new-instance v2, Lorg/json/JSONObject; invoke-direct {v2}, Lorg/json/JSONObject;-><init>()V invoke-interface {v1}, Ljava/util/Map;->keySet()Ljava/util/Set; move-result-object v0 invoke-interface {v0}, Ljava/util/Set;->iterator()Ljava/util/Iterator; move-result-object v3 :goto_2 invoke-interface {v3}, Ljava/util/Iterator;->hasNext()Z move-result v0 if-eqz v0, :cond_2 invoke-interface {v3}, Ljava/util/Iterator;->next()Ljava/lang/Object; move-result-object v0 check-cast v0, Ljava/lang/String; invoke-interface {v1, v0}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object; move-result-object v4 invoke-virtual {v2, v0, v4}, Lorg/json/JSONObject;->put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; :try_end_1 .catch Lorg/json/JSONException; {:try_start_1 .. :try_end_1} :catch_0 goto :goto_2 :catch_0 move-exception v0 invoke-virtual {v0}, Lorg/json/JSONException;->printStackTrace()V :goto_3 return-object v1 :cond_2 :try_start_2 new-instance v3, Lorg/json/JSONObject; invoke-direct {v3}, Lorg/json/JSONObject;-><init>()V invoke-static {}, Lcom/netease/cloudmusic/i/f;->f()Ljava/util/List; move-result-object v0 invoke-interface {v0}, Ljava/util/List;->iterator()Ljava/util/Iterator; move-result-object v4 :goto_4 invoke-interface {v4}, Ljava/util/Iterator;->hasNext()Z move-result v0 if-eqz v0, :cond_3 invoke-interface {v4}, Ljava/util/Iterator;->next()Ljava/lang/Object; move-result-object v0 check-cast v0, Lorg/apache/http/cookie/Cookie; invoke-interface {v0}, Lorg/apache/http/cookie/Cookie;->getName()Ljava/lang/String; move-result-object v5 invoke-interface {v0}, Lorg/apache/http/cookie/Cookie;->getValue()Ljava/lang/String; move-result-object v0 invoke-virtual {v3, v5, v0}, Lorg/json/JSONObject;->put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; goto :goto_4 :cond_3 const-string v0, "NwsSBxwDAAwK" invoke-static/range {v0 .. v0}, La/auu/a;->c(Ljava/lang/String;)Ljava/lang/String; move-result-object v0 invoke-virtual {v3, v0, p3}, Lorg/json/JSONObject;->put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; const-string v0, "LQsCFhwC" invoke-static/range {v0 .. v0}, La/auu/a;->c(Ljava/lang/String;)Ljava/lang/String; move-result-object v0 invoke-virtual {v2, v0, v3}, Lorg/json/JSONObject;->put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; invoke-interface {v1}, Ljava/util/Map;->clear()V const-string v0, "NQ8RExQD" invoke-static/range {v0 .. v0}, La/auu/a;->c(Ljava/lang/String;)Ljava/lang/String; move-result-object v0 invoke-virtual {v2}, Lorg/json/JSONObject;->toString()Ljava/lang/String; move-result-object v2 invoke-static {p1, v2}, Lcom/netease/cloudmusic/utils/NeteaseMusicUtils;->serialdata(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; move-result-object v2 invoke-interface {v1, v0, v2}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; :try_end_2 .catch Lorg/json/JSONException; {:try_start_2 .. :try_end_2} :catch_0 goto :goto_3 .end method
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| private static Map<String, String> a(String query, String path, Map<String, String> params, String requestId){ Map<String, Object> body; if (params == null) { body = new HashMap<>(); } else { body = new HashMap<>(params); } try { if (query != null) { String[] kvs = query.split("&"); for (int i = 0, len = kvs.length; i < len; i++) { String[] kv = kvs[i].split("="); body.put(kv[0], kv[1]); } } JSONObject paramObject = new JSONObject(); Iterator<String> paramIterator = body.keySet().iterator(); while (paramIterator.hasNext()) { String key = paramIterator.next(); paramObject.put(key, body.get(key)); } Iterator<Cookie> cookieIterator = f().iterator(); JSONObject header = new JSONObject(); while (cookieIterator.hasNext()) { Cookie cookie = cookieIterator.next(); header.put(cookie.getName(), cookie.getValue()); } header.put("requestId", requestId); paramObject.put("header", header); body.clear(); body.put("params", NeteaseMusicUtils.serialdata(path, paramObject.toString())); } catch (JSONException e) { } return body; }
|
编写程序
当你获取了网易云音乐的接口后,肯定是开始练手,编写程序业务。我自己搞了一下,效果图如下: