前言
前几天随手撸了个自动上传京东open token(open token其实就是用户cookie)的脚本(有兴趣可以看看:京东cookie自动更新维护),虽然可以自动上传了,但是每天都得去打开京东APP才行,而且两次打开时间不能超过24小时,不然open token就过期了。这方法怎么看都不是很省事!
最近一直在找替代方法,发现Zy143L大佬脚本中提到wskey可以申请open token,wskey有效期应该比较长,我使用好几天之前抓包到的wskey依然可以获取到open token。wskey在APP抓包中经常看到,使用wskey的请求里面都有签名,感觉签名方法应该是一样的。通过genToken
的接口获取open token,那个签名方法虽然短时间可以重复使用(请求只需要变动wskey就好,签名没有加入wskey做计算),但是还不清楚有效期,如果一直靠抓包获取签名的话那比上面直接抓包open token还恶心!
分析
wskey很重要,京东APP很多敏感接口都是使用wskey,不要泄露!!!
既然可以通过wskey获取open token,wskey有效期有比较长,那么它是一个很好代替每天上传open token的方法。
我的思路:
- 不定期通过手机app抓包wskey上传到服务器,服务器接收到后写入wskeys.list。
- 每天定时通过wskey换取open token(open token 24小时有效期,应该每天获取两次比较保险),并且写入cookies.list。
- 目前还不清楚wskey有效期,所以在第二步中wskey过期及时发送通知以便重新上传wskey。
经过一下午折腾,确实可行。但是看Zy143L大佬源码发现genToken
接口中的签名是请求coding.net返回的(应该是大佬自建部署的吧!)!这个动作引起了我的好奇心,没有公布签名算法,究竟是何等高级的玩意儿?请求没有任何数据,就一个get请求即可返回所有签名参数,难道签名参与计算的值都是固定的吗?
这类资料比较少,不过还是有乐于分享的大佬。找到几篇文章:
- JD app sign 加密参数破解 - unidbg
- 逆向工具之unidbg(在pc端模拟执行so文件中的函数)
- Unidbg使用指南(一)
- Unidbg模拟执行大厂so实操教程(一) 先把框架搭起来
- Unidbg模拟执行大厂so实操教程(二)
- 借鸡生蛋之SandHook的使用(一)
- 借鸡生蛋之某电商App签名so的使用(二)
- 借鸡生蛋之某电商App签名so的使用(三)
对于我来说难度比较大,涉及知识面广,不过如果有信心那就没问题。
我总结了一下经过步骤:
- 首先得搭建一个android开发环境。
- 下载京东安卓APP,apk文件可以当作压缩文件打开,从lib里面提取出libjdbitmapkit.so。
- 用ida修改libjdbitmapkit.so文件,需要修改一些地方屏蔽错误。
- 使用unidbg载入libjdbitmapkit.so,调用签名方法签名。
- 打包编译,部署到服务器。
步骤虽然简单,但是操作过程对于一个从来没有接触过andriod、汇编的人来说每一步都掉坑!!!
过大坑纪要
-
Exception in thread "main" java.lang.IllegalStateException: Illegal JNI version: 0xffffffff,这个问题坑了我好久,起初我以为自己环境有问题,因为根本找不到什么资料。其实就是so文件问题,如果你没有使用unidbg-0.9.1版本的话会出现这个错误,引发错误的根本原因就是先按借鸡生蛋之某电商App签名so的使用(三)对so文件打补丁,如果按上文推荐的文章顺序阅读那么就会被卡住,建议全部读一遍后再动手。
-
打补丁使用的ida软件,对于汇编是一脸懵逼的,借鸡生蛋之某电商App签名so的使用(三)一文中把修改位置写得很明白了,但是ida真心不会用,其中包括安装ida插件:
-
keystone中下载msi安装文件,Version 0.9.1有提供32位和64位装一个就行。安装过程一定要勾选安装pthon2.7,我就取消了,最后插件无法使用,还是自己手动去安装的,如果你也是手动安装记得安装six模块:
pip install six
。 -
ida中
Options-General-Disassembly-Number of opcode bytes
设置成16,以十六进制显示字节码:
-
使用keypatch插件修改
sourceDir
部分,JNI_OnLoad
部分使用ida功能Edit-Patch program-Change byte
就好了。000037CC 04 25 C0 F2 01 05 MOVS R5, #0x10004
这段我是用keypatch一直报错,浪费一两个小时,使用Patch program
替换中间字节就行了:
修改摘要:00002D08 40 F0 40 81 BNE.W loc_2F8C -> 00002D08 40 E1 B loc_2F8C 00002F8C 6F F0 02 00 MOV R0, #0xFFFFFFFD -> 00002F8C 4F F0 01 00 MOV.W R0, \#1 000037C8 FF F7 96 F9 BL check_status -> 000037C8 FF F7 96 F9 BL check_status 000037C8 FF F7 96 F9 BL check_status 000037CC 80 46 MOV R8, R0 000037CE 00 28 CMP R0, \#0 000037D0 67 D1 BNE loc_38A2 -> 000037C8 FF F7 96 F9 BL check_status 000037CC 04 25 C0 F2 01 05 MOVS R5, #0x10004 000037D2 2A E0 B loc_382A;
-
-
打包一定要看Unidbg使用指南(一),所有操作都是在
test
文件夹完成的,不然会缺少包,装又装不上!打包也要载入test文件夹!
搭建服务
如果你能坚持到这步就已经成功了,只要能获取到签名参数想怎么玩就怎么玩!我还是用http服务比较顺手,网上找了个java socket代码一顿改!!!
在JD app sign 加密参数破解 - unidbg文中代码基础上修改,加入socket模拟http:
...
public static void service(final Socket socket, final JingDong jd) {
InputStream inSocket;
try {
//获取HTTP请求头
inSocket = socket.getInputStream();
int size = inSocket.available();
byte[] buffer = new byte[size];
inSocket.read(buffer);
String request = new String(buffer);
System.out.println("ClientBrowser:\n" + request + "\n"
+ "------------------------------------------------------------------");
String httpCode = "200 OK";
String result = "";
if (request.length() > 0) {
try {
JSONObject data = JSON.parseObject(request.substring(request.lastIndexOf("\r\n")).trim());
if (data != null) {
String uuid = data.getString("uuid");
String functionId = data.getString("functionId");
try {
String body = new URLCodec().decode(data.getString("body"));
String version = data.getString("version");
if (!uuid.isEmpty() && !functionId.isEmpty() && !body.isEmpty() && !version.isEmpty()) {
result = convert(jd.runJni(uuid, functionId, body, version));
System.out.println(result);
System.out.println(convert(result));
}
} catch (DecoderException e) {
e.printStackTrace();
}
}
} catch (JSONException e) {
e.printStackTrace();
result = "{\"code\": \"400\", \"message\": \"Invalid param\"}";
httpCode = "400 Bad Request";
}
}
//将响应头发送给客户端
String responseFirstLine = "HTTP/1.1 " + httpCode + "\r\n";
String responseHead = "Content-Type:application/json\r\n";
OutputStream outSocket = socket.getOutputStream();
System.out.println("ServerResponse:\n" + responseFirstLine + "\n" + responseHead + "\n" + result + "\n"
+ "--------------------------------------------------------------------");
System.out.println("current thread:" + Thread.currentThread().getName());
System.out.println("threads count:" + Thread.activeCount());
outSocket.write(responseFirstLine.getBytes());
outSocket.write(responseHead.getBytes());
outSocket.write("\r\n".getBytes());
outSocket.write(result.getBytes());
outSocket.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
//獲取參數,服務監聽地址和端口
//java -Daddress=0.0.0.0 -Dport=9999 -jar unidbg-android.jar
address = System.getProperty("address", "127.0.0.1");
port = Integer.parseInt(System.getProperty("port", "8668"));
threadCount = Integer.parseInt(System.getProperty("thread", "10"));
//綫程池
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
final ServerSocket serverSocket;
final JingDong jd = new JingDong();
try {
//建立服务器Socket,监听客户端请求
serverSocket = new ServerSocket(port, 50, InetAddress.getByName(address));
System.out.println("Server is running on " + serverSocket.getLocalSocketAddress());
//死循环不间断监听客户端请求
while (true) {
final Socket socket = serverSocket.accept();
System.out.println("biuld a new tcp link with client,the cient address:" +
socket.getInetAddress() + ":" + socket.getPort());
//并发处理HTTP客户端请求
executorService.execute(new Runnable() {
@Override
public void run() {
service(socket, jd);
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jd.destroy();
executorService.shutdown();
}
}
...
网上七拼八凑折腾出来这么个玩意,使用:
java -Daddress=127.0.0.1 -Dport=8668 -DthreadCount=5 -jar xxx.jar
address
:绑定地址,port
:绑定端口,threadCount
:线城数量。
请求参数:
//没有限制请求方法,GET POST随意,以下是请求body
{"uuid":"随机字符串,随机生成就行了","functionId":"接口(genToken)","body":"URI编码的json,不要带任何多余东西","version":"安卓版本"}
//返回结果
{
"uuid": "9d53afe389f6ae5f",
"st": "1630922926986",
"sign": "0a9b72a4fc6ffd5d799642dd295623f8",
"sv": "111"
}
然后外面再套一层nginx反代,或者和自己jd_script同服务器,内网请求。你要是绑定0.0.0.0暴漏出去也行!
结语
为了个签名折腾了好久,整个签名使用了functionId,body,uuid,client,clientVersion五个参数,client基本可以固定android了。使用ida汇编长了不少知识。
至此应该是不用在操心京东登陆方面的问题了,只要使用京东APP全部都自动更新了!
可能JD 觉得大数据风控就够了 导致最新版的JD算法还是没变
我觉得不可能大改,除非有更安全高效的算法!再一个无论怎么变都可以被逆向的,只要算法是安全的就没有必要花费太多精力在这上面!
Sign计算是计算出来了,但提交后返回的是{"code":"600","echo":"signature verification failed"}”,这是什么问题呢
签名算法变了!
原来如此,新的签名算法你研究出来了吗
https://github.com/Zy143L/wskey 有现成的用!
大佬,你自己算SIGN的API可以分享下吗~~
签名算法已经变了。https://github.com/Zy143L/wskey 这个项目里面有,看代码!
(agent) [408443] Arguments com.jingdong.common.utils.BitmapkitUtils.getSignFromJni(com.jingdong.app.mall.JDApp@944bdee, getShopHomeActivityInfo, {"RNVersion":"0.59.9","abtest":"","cid":"17105374","cname":"米面粮油","complexSource":"app-productDetail","lat":"25.074035","latWs":"25.074263","lng":"101.499347","lngWs":"101.499748","shopId":"11317694","source":"app-s,"sourceRpc":"shop_app_home_allProduct","venderId":"11578734"}, f7b5b779d8eed0ef, android, 10.3.5)
(agent) [408443] Return Value: st=1643911743825&sign=57ba1527014b975f01df1ae53845942e&sv=101
(agent) [408443] Return Value: st=1643911743826&sign=86fd7056104f2b85a2ba68ee24888e3b&sv=122
大佬我目前到这一步,getSignFromJni函数算出来的不行了吗?
主要想 有了SGIN 配合监听 谢谢 京东活动的脚本
这个应该是没变,但是在这个基础上加强了!
我先试试大佬 有问题和你探讨~~~~
时隔三个多月,挖下,打扰了,老哥
不知老哥你之后研究完jd app的签名算法了么??才入坑逆向不到一个月,有些急于求成,近一段时间拿jd 来练手,才翻到大佬的这篇文章,继而看到老哥你在2月初发的评论提问。。。
如果老哥之后解决了“getSignFromJni函数”这部分算不出来的问题的话,麻烦分享下最新的解决方法呗;亦或者老哥你将研究出的解决方法分享到你的个人博客 或你的github 个人仓库的话,麻烦指个路呗
我没有研究签名算法了,因为有大佬分享了他做好的api!签名算法也不是我逆向出来的,文章中有提到我参考的文章,最新的他也逆向出来了,因该是变种base64!具体你可以参考他的文章。
你好 这边我也是出现了和你一样的问题
Exception in thread "main" java.lang.IllegalStateException: Illegal JNI version: 0xffffffff
so文件也是按照大佬的步骤改的 需要添加的环境也添加了 老是报这个错
要对so文件打补丁
你好,你最后Unidbg使用的还是0.9.1版本是吧?最新版是不是还是不行?
你好,patch好的so文件方便发一下吗?我老是改不对
大佬,Convert method 代码可以贴而一下吗
能否透露下app版本..我拿10.X的改..ida里面改完了..但是实际还是会报错
我都是下最新版本
最新的版本10.1.6 有新的变化了吗?怎么总是通不过
什么东西版本10.1.6?
大佬,可以给份服务器源码吗