0%

[TOC]

环境

  • Mac
  • Android SDK:
1
/Users/zhoujie/Library/Android/sdk
  • JDK
1
/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home
  • 夜神模拟器
  • 真机 - HONOR20
1
2
3
4
Android 版本: 10

adb shell getprop ro.product.cpu.abi
arm64-v8a

adb

  • 理解adb start-server
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //没有相关进程
    ➜ ~ ps aux | grep adb

    //没有相关端口监听
    ➜ netstat -nat | grep 5037

    ➜ ~ adb start-server
    * daemon not running; starting now at tcp:5037
    * daemon started successfully

    ➜ ~ ps aux | grep adb
    zhoujie 76412 0.0 0.0 34195628 5592 ?? Ss 10:27PM 0:00.23 adb -L tcp:5037 fork-server server --reply-fd 4

    ➜ ~ netstat -nat | grep 5037
    tcp4 0 0 127.0.0.1.5037 *.* LISTEN
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
// 电脑上有adb进程
➜ ~ ps aux | grep adb
zhoujie 99607 0.9 0.0 4354316 3004 ?? S adb -P 5037 fork-server server --reply-fd 13

// 手机上有adbd进程
➜ ~ adb shell ps | grep adb
root 1770 1 11420 1276 c6716cc0 S /sbin/adbd

service.adb.tcp.port
- > 0:adbd将监听网络对应的端口(一般为5555)
- <=-1:adbd将监听USB

// 手机上运行设置
setprop service.adb.tcp.port 5555

// 电脑上运行设置
adb tcpip 5555

// 指定adb server的网络端口
// adb的默认端口为 5037
adb -P <port> start-server

// adb设置全局代理
adb shell settings put global http_proxy IP地址:端口号

// 移除代理
adb shell settings delete global http_proxy
  • 电脑上adb进程相关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 查看adb进程
➜ ~ ps aux | grep adb

// 启动adb进程
➜ ~ adb start-server
* daemon not running; starting now at tcp:5037
* daemon started successfully

// 指定adb server的网络端口
// adb进程的默认端口为 5037
➜ ~ adb -P <port> start-server

➜ ~ ps aux | grep adb
zhoujie 943 adb -L tcp:5037 fork-server server --reply-fd 4

➜ ~ netstat -nat | grep 5037
tcp4 0 0 127.0.0.1.5037 *.* LISTEN

// 杀死adb进程
// adb kill-server : kill the server if it is running
➜ ~ adb kill-server

// 启动adb进程-指定adb server的网络端口
adb -P <port> start-server
  • 连接模拟器或真机
    1
    2
    3
    4
    连接夜神模拟器:adb connect 127.0.0.1:62001
    连接MUMU模拟器:adb connect 127.0.0.1:22471[Mac]
    连接MUMU模拟器:adb connect 127.0.0.1:7555[windows]

阅读代码

  • 界面
1
2
// 普通真机也有效
adb shell dumpsys activity top
  • 搜索词 - 网络
1
implements Interceptor
  • 搜索词 - 加密
1
2
Cipher,SecretKeySpec,IvParameterSpec 
"AES
  • 退出
1
android.os.Process.killProcess(android.os.Process.myPid())

查看日志

真机用户手机也可以查看日志

1
2
3
4
5
6
// 1. 获取到进程ID
adb shell ps | grep com.jason
u0_a527 1876 673 1744336 148208 0

// 2. 查看日志
adb logcat --pid=1876

任务:查壳、脱壳

1
2
3
4
5
Android 5.0开始默认是ART模式
libart.so - Openmemory函数

Android 4.4以下是Dalvik模式
libdvm.so - dvmDexFileOpenPartial函数

任务:可调试

  • 工具:mprop

方法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
手机上执行:
// 查看
# getprop ro.debuggable
0
// 修改为 全部App都可以 调试
# ./mprop ro.debuggable 1

# getprop ro.debuggable
1

文件:/default.prop
ro.debuggable=0
文件:/system/build.prop

方法二:

1
2
// AndroidManifest.xml
<application android:debuggable="true" ...
1
am start -D -n com.outdoor.debugtest/.MainActivity

任务:webview可调试

1
2
3
4
5
var WebView = Java.use("android.webkit.WebView")
WebView.setWebContentsDebuggingEnabled.overload("boolean").implementation = function (s) {
console.log("\n[*] WebView.setWebContentsDebuggingEnabled")
this.setWebContentsDebuggingEnabled(true)
}

任务:签名

  • 下载 抖音极速版
1
2
3
4
从 小米应用商店 下载 抖音极速版
https://app.mi.com/details?id=com.ss.android.ugc.aweme.lite&ref=search

com.ss.android.ugc.aweme.lite.apk
  • 在模拟器上安装 原版抖音极速版 并运行,确保能正常运行
  • 删除原签名,重新打包
1
2
3
4
5
6
7
8
9
10
11
mkdir com.ss.self

unzip com.ss.android.ugc.aweme.lite.apk -d com.ss.self

// 删除 META-INF 目录
rm -rf com.ss.self/META-INF


// 打包成Apk - com.ss.self.apk
cd com.ss.self
zip -r com.ss.self.apk *
  • 签名
1
2
3
4
5
// 进入到 apksigner 所在目录
cd /Users/zhoujie/Library/Android/sdk/build-tools/29.0.2

// 签名
./apksigner sign --ks ~/.keystore ~/Downloads/com.ss.self/com.ss.self.apk
  • 在模拟器上安装 重新签名的Apk 并运行,确保能正常运行
1
2
adb connect 127.0.0.1:62001
adb install ~/Downloads/com.ss.self/com.ss.self.apk
  • 一步到位
1
./apksigner sign --ks ~/.keystore ~/Downloads/com.ss.android.ugc.aweme.lite.apk

任务:信任用户证书

  • 在清单文件AndroidManifest.xml中开启网络安全配置,代码如下:
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
  • 新建文件res/xml/network_security_config.xml来进行网络安全的配置,通过trust-anchors来设置信任的证书,代码如下:
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="user" />
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>

任务:修改代码

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
// 1. 反编译出源代码
➜ apktool d bxd-app-release.apk
I: Using Apktool 2.5.0 on bxd-app-release.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /Users/zhoujie/Library/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Copying META-INF/services directory

// 2. 修改smail代码 或 资源文件

// 3. 重新打包
➜ apktool b bxd-app-release
I: Using Apktool 2.5.0
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
W: Unknown file type, ignoring: bxd-app-release/smali/.DS_Store
W: Unknown file type, ignoring: bxd-app-release/smali/com/.DS_Store
W: Unknown file type, ignoring: bxd-app-release/smali/com/jason/.DS_Store
I: Checking whether resources has changed...
I: Building resources...
I: Copying libs... (/META-INF/services)
I: Building apk file...
I: Copying unknown files/dir...
I: Built apk...

任务:注入frida-gadget.so

  • 工具:objection,LIEF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 
objection patchapk -2 -a arm64-v8a -s xxx.apk

//
objection patchapk --help

Patch an APK with the frida-gadget.so.
Options:
-s, --source TEXT The source APK to patch [required]
-a, --architecture TEXT The architecture of the device the patched
APK will run on. This can be determined with
`adb shell getprop ro.product.cpu.abi`. If it
is not specified, this command will try and
determine it automatically.
-2, --use-aapt2 Use the aapt2 binary instead of aapt as part
of the apktool processing.
  • 技巧:获取-a的正确值
1
adb shell getprop ro.product.cpu.abi
  • 技巧:获取-a的所有可取值
1
2
3
// 输入一个错误的值
objection patchapk -2 -a a -s 202103.apk
Exception: Invalid architecture `a` set. Valid options are: armeabi, armeabi-v7a, arm64, arm64-v8a, x86, x86_64
  • 细节:objection把libfrida-gadget.so下载在:
1
2
~/.objection/android/arm64/libfrida-gadget.so
~/.objection/android/arm64-v8a/libfrida-gadget.so
  • 参考

Gadget
https://frida.re/docs/gadget/

09 - How to use frida on a non-rooted device
https://lief.quarkslab.com/doc/latest/tutorials/09_frida_lief.html

任务:so相关

记一次unicorn半自动化逆向——还原某东sign算法

模拟器危险

  • 彩蛋视频
  • 快看点
  • 多看点
  • 红云视频极速版
  • 妙看赚钱-com.taige.mygold-libsecuritydevice.so

加固的App

问题:Apktool回编译失败

  • /res/layout/activity_main.xml:19: error: attribute android:abc not found
1
2
3
4
W: /Users/zhoujie/Downloads/bxd-app-release/res/layout/activity_main.xml:19: error: attribute android:abc not found.
W: error: failed linking file resources.
brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [/var/folders/q5/g49y_m_16t967y5b7z04fyvr0000gn/T/brut_util_Jar_158703877894311218821877137576166888159.tmp, link, -o, /var/folders/q5/g49y_m_16t967y5b7z04fyvr0000gn/T/APKTOOL10058866066230304687.tmp, --package-id, 127, --min-sdk-version, 21, --target-sdk-version, 29, --version-code, 1, --version-name, 1.0, --no-auto-version, --no-version-vectors, --no-version-transitions, --no-resource-deduping, -e, /var/folders/q5/g49y_m_16t967y5b7z04fyvr0000gn/T/APKTOOL9926499678324403846.tmp, -0, arsc, -I, /Users/zhoujie/Library/apktool/framework/1.apk, --manifest, /Users/zhoujie/Downloads/bxd-app-release/AndroidManifest.xml, /Users/zhoujie/Downloads/bxd-app-release/build/resources.zip]
Rebuilding process exited with code 1
  • 原因:xml中故意加入无效的attribute
  • 解决:删除xml中无效的attribute,再回编译

[TOC]

博客

https://saucer-man.com/archives.html
https://www.hackliu.com/

关于Linux下ASLR与PIE的一些理解

一步一步学ROP之linux_x86篇
一步一步学ROP之linux_x64篇
一步一步学ROP之gadgets和2free篇

图书

Python渗透测试编程技术:方法与实践

0day安全:软件漏洞分析技术(第2版)

Metasploit渗透测试指南

Web前端黑客技术揭秘

安全技术大系

Python编程

靶场

xctf - 攻防世界
https://adworld.xctf.org.cn/task

pwnable.kr
http://pwnable.kr/play.php

hack the box
https://www.hackthebox.eu/

https://github.com/vulhub/vulhub

https://portswigger.net/web-security

OWASP BWA(Broken Web Apps)

SQLi-CTF

  1. https://github.com/Corb3nik/SQLi-CTF
  2. http://sqli.bihuo.cn/

DVWA
1.

1
2
3
4
docker pull registry.cn-shanghai.aliyuncs.com/yhskc/dvwa
docker run -d -p 0.0.0.0:80:80 registry.cn-shanghai.aliyuncs.com/yhskc/dvwa

登录:admin - password
  1. http://dvwa.bihuo.cn/login.php

nanhack
http://www.nanhack.com/

VulApps
https://github.com/Medicean/VulApps

bwapp

1
2
3
4
5
docker pull registry.cn-shanghai.aliyuncs.com/yhskc/bwapp
docker run -d -p 0.0.0.0:80:80 registry.cn-shanghai.aliyuncs.com/yhskc/bwapp

访问下面网址
http://localhost/install.php

攻略

[TOC]

博客

r0ysue
渔滒
皇帝大大
白龙~

图书

Android应用安全防护和逆向分析
Android安全攻防权威指南
第一行代码(第3版)
Android编程权威指南

文章

汇编


Android


Hook


Unicorn & Unidbg


Frida


Xposed


IDA Pro

动态调试


AST & 编译技术



视频

零基础入门Android(安卓)逆向

抓包

仓库/脚本

https://codeshare.frida.re/

frida
https://github.com/frida/frida

objection - runtime mobile exploration
https://github.com/sensepost/objection

jadx - Dex to Java decompiler
https://github.com/skylot/jadx

FridaContainer
https://github.com/deathmemory/FridaContainer

r0capture - 安卓应用层抓包通杀脚本
https://github.com/r0ysue/r0capture

脱壳

靶场

https://scrape.center/

Python库

node库

1
npm install @babel/core @babel/parser @babel/traverse @babel/generator
  • @babel/core:Babel 编译器本身,提供了 babel 的编译 API
  • @babel/parser:将 JavaScript 代码解析成 AST 语法树
  • @babel/traverse:遍历、修改 AST 语法树的各个节点
  • @babel/generator:将 AST 还原成 JavaScript 代码
  • @babel/types:判断、验证节点的类型、构建新 AST 节点等

工具

visUAL2 专业的ARM汇编教学工具
Compiler Explore 强大的在线编译器
AST Explorer

退出进程

  • kill(pid, 9)
  • android.os.Process.killProcess(android.os.Process.myPid())
  • System.exit(0)

[TOC]

文章

AST

验证码

其他

博客

视频

框架和库

练习平台

[TOC]

Docker

Go

Python

Flask

kubernete

Linux

漫画:如何在数组中找到和为 “特定值” 的三个数?
漫画:如何在数组中找到和为 “特定值” 的两个数?
漫画:什么是 “灰犀牛事件” ?
漫画:如何螺旋遍历二维数组?(修订版)
漫画:如何螺旋遍历二维数组?
漫画:什么是 “黑天鹅事件” ?
漫画:什么是 “抽象工厂模式” ?
漫画:设计模式之 “工厂模式”
漫画:程序员公开 “怼老板” 的下场
漫画:什么是 “跳表” ?
漫画:设计模式之 “外观模式”
漫画:什么是 “小镇做题家” ?
漫画:什么是 “设计模式” ?
漫画:什么是 “职场PUA” ?
漫画:设计模式中的 “观察者模式”
《漫画算法》终于出电子书了!
漫画:什么是 “牛奶咖啡” 问题?
漫画:什么是AVL树?(修订版)
漫画:什么是平衡二叉树?
漫画:赌王何鸿燊与神秘的“凯利公式”
漫画:什么是红黑树?(整合版)
漫画:什么是红黑树?(下篇)
漫画:什么是红黑树?
漫画:什么是 “眼镜蛇效应” ?
漫画:“哈夫曼编码” 是什么鬼?
漫画:什么是openEuler社区?
漫画:什么是流行病的 R0 和 R ?
漫画:什么是 “哈夫曼树” ?
漫画:什么是 “模因” ?
漫画:什么是 “智猪博弈” ?
漫画:什么是KMP算法?
漫画:如何优化 “字符串匹配算法”?
漫画:程序员的春节
漫画:“新型冠状病毒” 是什么鬼?
漫画:什么是字符串匹配算法?
漫画:面试官被他老婆痛骂了 。。。
漫画:什么是 哥德巴赫猜想?
漫画:“排序算法” 大总结
漫画:程序员也爱跑马拉松吗?
漫画:互联网公司会议观察
漫画:三种 “奇葩” 的排序算法
漫画:什么是区块链?
小灰的《漫画算法》,入选了“当当20周年主荐品”!
漫画:什么是基数排序?
漫画:程序媛在北京
漫画:去掉一个数,如何让剩余的数乘积最大?
漫画:什么是狭义相对论?
漫画:用户的嘴,骗人的鬼!
漫画:什么是归并排序?
漫画:要跳槽?这道缓存设计题你有必要看看!
漫画:什么是摩尔斯电码?
漫画:上海程序员 图鉴
漫画:什么是中台?
漫画:什么是希尔排序?
漫画:什么是旅行商问题?
漫画:什么是囚徒困境?
《漫画算法》终于出电子书了!
漫画:什么是插入排序?
漫画:职场画饼记
漫画:职场画饼记
漫画:如何找到两个数组的中位数?(修订版)
漫画:如何找到两个数组的中位数?
漫画:有趣的“帽子问题”
漫画:什么是选择排序?
漫画:什么是最小生成树?
漫画:为什么程序员没有女友?
漫画:反直觉的 “三门问题”
漫画:“旋转数组”中的二分查找
漫画:什么是二分查找?(修订版)
漫画:什么是二分查找?
漫画:面试官考我图形推理题,我该怎么办?
漫画:有趣的 “切蛋糕“ 问题
小灰的《漫画算法》全面上架!
漫画:图的 “多源” 最短路径
漫画:Dijkstra 算法的优化
漫画:996的本质是什么?
漫画:程序员和产品经理的相爱相杀
漫画:图的 “最短路径” 问题
漫画:深度优先遍历 和 广度优先遍历
漫画:什么是 “图”?(修订版)
漫画:什么是 “图”?
漫画:女生适合做程序员吗?
漫画:什么是 HTTPS 协议?
漫画:如何将一个链表“逆序”?
漫画:为什么新疆永远不包邮?
漫画:程序员的春节
漫画:什么是加密算法?
漫画:程序员的春节
漫画:什么是加密算法?
漫画:有趣的海盗问题 (完整版)
漫画:有趣的【海盗】问题
漫画:程序员之间的真爱,好暖啊!
漫画:寻找无序数组的第k大元素(修订版)
漫画:寻找无序数组的第k大元素
漫画:成功的年终汇报,你需要注意些什么?
漫画:如何实现大整数相乘?(整合版)
漫画:如何实现大整数相乘?(下)
漫画:如何实现大整数相乘?(上) 修订版
漫画:如何实现大整数相乘?(上)
漫画:为什么程序员收入高,却这么低调?
漫画:一道数学题引发的血案
漫画:如何实现大整数相加?(修订版)
漫画:如何实现大整数相加?
漫画:删去k个数字后的最小值
漫画:什么是LRU算法?
漫画:如何用栈实现队列?
漫画:什么是桶排序?
漫画:什么是计数排序?
漫画:做区块链能赚大钱吗?
漫画:什么是优先队列?
漫画:程序员学算法有什么用?
漫画:什么是堆排序?
漫画:什么是二叉堆?(修正版)
漫画:什么是二叉堆?
漫画:什么是时间复杂度?
漫画:什么是快速排序?(完整版)
漫画:什么是快速排序?(上)
漫画:什么是鸡尾酒排序?(修订版)
漫画:什么是鸡尾酒排序?
漫画:出色的程序员具有哪些特质?
漫画:什么是冒泡排序?
漫画:动态规划解决扔鸡蛋问题
漫画:有趣的扔鸡蛋问题
漫画:什么是协程?
漫画:足球和世界杯的历史
漫画:如何用Zookeeper实现分布式锁?
漫画历史:人类的诞生
漫画:什么是分布式锁?
漫画历史:地球的新生
漫画:什么是ZooKeeper?
漫画历史:恐龙时代
漫画:远古的地球
漫画:什么是宇宙大爆炸?
漫画:什么是拜占庭将军问题?
漫画:什么是SnowFlake算法?
漫画:如何实现抢红包算法?
漫画:什么是字典序算法?
漫画:什么是蓝绿部署?
漫画:如何搞定难相处的队友?
漫画:什么是分布式事务?
漫画:程序员的最强大脑
漫画:“架构师”小赵的故事
漫画:什么是服务熔断?
漫画:什么是八皇后问题?
漫画:第一次去女友家过年的程序员
漫画:第一次去女友家过年的程序员
漫画:什么是MapReduce?
漫画:什么是数据仓库?
漫画:什么是区块链?
漫画:女生适合做程序员吗?
漫画:什么是微服务?
漫画:如何学习人工智能?
漫画:什么是CAS机制?(进阶篇)
漫画:面试过程的神回复
漫画:什么是 CAS 机制?
漫画:程序员调Bug的真实写照
漫画:什么是架构师?
漫画解读软件开发模式
漫画:什么是佛系程序员?
漫画:什么是volatile关键字?(整合版)
漫画:volatile对指令重排的影响
漫画:什么是 volatile 关键字?
漫画:什么是单例模式?(整合版)
漫画:如何写出更优雅的单例模式?
漫画:什么是单例设计模式?
漫画:什么是ConcurrentHashMap?
漫画:高并发下的HashMap
漫画:什么是HashMap?
漫画:什么是红黑树?
漫画:为什么月薪5W的程序员活得像月薪5K?
漫画:AES算法的底层原理
漫画:什么是AES算法?
漫画:什么是SHA系列算法?
漫画:什么是AES算法?
漫画:什么是SHA系列算法?
漫画:如何破解MD5算法?
漫画:什么是MD5算法?
漫画:什么是Base64算法?
漫画:当程序员遇上智力题(第四季)
漫画:什么是布隆算法?
漫画:Bitmap算法 整合版
漫画:Bitmap算法 进阶篇
漫画:什么是Bitmap算法?
漫画:什么是一致性哈希?
漫画:什么是B+树?
漫画:什么是B-树?
漫画:什么是跳跃表?
漫画:什么是动态规划?(整合版)
漫画:什么是动态规划?
漫画:什么是中间人攻击
漫画:编程其实是文科
漫画:三分钟了解敏捷开发
漫画:优秀的程序员具备哪些属性?
漫画算法:无序数组排序后的最大相邻差值
漫画:当程序员遇上智力题(第三季)
漫画:什么是人工智能?
漫画:程序员不是修电脑的!
漫画算法:判断2的乘方
漫画:什么是大数据?
漫画:程序员不是修电脑的!
漫画算法:判断2的乘方
漫画:什么是大数据?
漫画:什么是机器学习?
漫画:程序员的智商测试题
漫画算法:最小栈的实现
漫画:如何用脚本抢月饼?
漫画:如何用脚本抢月饼?(预告)
漫画算法:找出缺失的整数
漫画算法:辗转相除法是什么鬼?
漫画算法:如何判断链表有环?
漫画:如何用脚本抢月饼?(预告)
漫画算法:找出缺失的整数
漫画算法:辗转相除法是什么鬼?
漫画算法:如何判断链表有环?

[TOC]

需求

很多手机App都有做任务,得金币兑换现金的功能
手动做任务有很多限制;写代码自动化运行可以消除一些限制
但写代码时的敲键盘有点费时间,这篇文章就是要解决自动生成代码的问题

运行环境

背景知识: python、mitmproxy、jinja2
python: 3.8.1
python第三方库: mitmproxy, requests, Jinja2
手机代理地址: mitmproxy的地址

因为有多个文件,这里贴部分代码,完整代码地址:
https://github.com/zhoujie903/LearnPython/tree/master/mitmproxy_addons/gen_code

使用说明

步骤

设置:
设置1:设置self.api_dir: 存放App目录的父目录
代码目录下的gen_code_mitm.py

1
2
3
4
5
class GenCode(object):
def __init__(self):
ctx.log.info('__init__')
# 设置代码文件生成的目录\文件夹
self.api_dir = '/Users/zhoujie/Desktop/api/'

设置2: 后面说明…

步骤:

  1. 电脑上运行起mitmproxy;手机设置代理地址为mitmproxy的地址
  2. 打开手机App,正常操作:点击、滑动触发相应网络请求被触发,这时各个api方法代码已生成<api-name>.text
  3. 正常结束mitmproxy,这时在done方法里会生成整体代码code-<app-name>.py
  4. 微修改code-<app-name>.py并运行

步骤说明:
步骤1: 启动mitmproxy

1
mitmdump --set session='huawei' -s "gen_code_mitm.py"

session的值在后面说明…

设置

gen_code_mitm.py并不会自动生成全部网络请求的代码,只会生成已配置好的网络请求的代码

配置如下:

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
class Api(object):
...
class GenCode(object):
def __init__(self):
ctx.log.info('__init__')
...

# 趣头条
# 步骤1: urls里增加用URL path代表的网络请求
urls = [
r'x/tree-game/',
Api(r'/app/re/taskCenter/info/v1/get',params_as_all=True), ,
]

# 步骤2: 创建代表App的App对象
self.qu_tou_tiao = App(urls, 'qu-tou-tiao')

# 百度 - 好看视频
urls = [...]
self.hao_kan = App(urls, 'hao-kan')

# 步骤3: 把App添加进来
self.flowfilters = [
self.qu_tou_tiao,
self.hao_kan,
...
]
    1. urls里增加用URL path代表的网络请求
1
2
3
urls = [
r'x/tree-game/',
]
    1. 创建代表App的App对象
1
2
# 'qu-tou-tiao'中给App取的名字
self.qu_tou_tiao = App(urls, 'qu-tou-tiao')
    1. 把App对象加入到self.flowfilters中,代表要生成这个App的代码
1
2
3
self.flowfilters = [
self.qu_tou_tiao,
]

urls的配置在后面说明…

目录/文件夹

先约定2个概念:代码目录App目录
代码目录:生成App目录的代码、模板等文件的目录
App目录:保存自动生成的App代码文件的目录

代码目录有2类文件:

  • *.py    - 代码文件 `gen_code_mitm.py`
  • *.j2.py - 模板文件

App目录有2类文件:

  • *.py - 代码文件:sessions.py、code-xxx.py、session_xxx.py
  • *.text - api文件,包含2个版本的代码文件、响应http_resopnse_body,不会被py文件读取
1
2
3
4
依赖关系[import关系]
code-xxx.py
sessions.py
session_xxx.py
  • session_xxx.py: 保存着像账号ID、cookie、token等用户登录数据

  • 每个App都有自己的目录: 比如这里有 今天头条极速版本[‘jin-ri-tou-tiao’], 趣头条[‘qu-tou-tiao’] 两个目录/文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
➜  api tree
.
├── jin-ri-tou-tiao
│ ├── api_news_feed_v47.text
│ ├── code-jin-ri-tou-tiao.py
│ ├── search_suggest_homepage_suggest.text
│ ├── session_huawei.py
│ ├── session_ios.py
│ ├── session_xiaomi.py
│ └── sessions.py
└── qu-tou-tiao
├── app_re_taskcenter_info_v1_get.text
├── code-qu-tou-tiao.py
├── xxx.json
├── session_huawei.py
├── session_xiaomi.py
├── sessions.py
└── sign_sign.text

2 directories, 40 files
  • 每个App会有多份session_xxx.py:比如session_huawei.py, session_ios.py, session_xiaomi.py; 表示有华为、iPhone、小米三台手机的运行数据

session

这里有一个需求与实现方案的问题:

需求: 一份代码[code-xxx.py] - 维护成本低; 多份账号数据[session_xxx.py] - 批量运行, 效率高
问题: 一个网络请求的数据怎么知道写入哪个账号数据文件中[session_xxx.py]
解决:

  • 推断: 从网络请求的headers、parameters、query、body中的值来推断为某个session
  • 指定: 启动时指定为某个session

先约定1个概念:一次mitmproxy运行
一次mitmproxy运行: 启动mitmproxy -> 1或多部手机操作相同App ->退出\停止mitmproxy[ctrl+c或Q]

方案一: 指定

在启动时指定--set session='huawei', 则所有数据写入session_huawei.py文件中

1
mitmdump --set session='huawei' -s "gen_code_mitm.py"

问题: 多部手机[代表不同账号]操作相同App时,多个账号数据都写入到了同一个账号文件中[session_huawei.py]

最佳实践: 一次mitmproxy运行只操作一个手机[一个账号]

方案二: 推断
在启动时不指定session, 代码自动推断

1
mitmdump -s "gen_code_mitm.py"

举例:
今日头条极速版有一个api: /search/suggest/homepage_suggest/
在parameters\query有"device_platform": "iphone"字段值, 那就写入session_ios.py中。

问题:
如果有一个api无法从headers、parameters、query、body等信息中判断出该写入哪个文件?

这时会写入session_default.py文件中,这时一个账号的数据被写到2个文件中: session_default.py、session_xxx.py, 生成的代码会被认为是2个账号,无法”正常运行”

最佳实践: 一次mitmproxy运行只操作一个手机[一个账号], 且启动时设置guess_as_session

1
mitmdump --set guess_as_session='huawei' -s "gen_code_mitm.py"

guess_as_session不能像session那样设定任意值
guess_as_session的取值:ios, huawei, xiaomi [自己手头只有这些设备]
若要guess_as_session可以取其它值,需要设置:

gen_code_mitm.py - class GenCode(object) - __init__方法:

1
2
3
4
5
6
7
self.guess_session = collections.OrderedDict(
ios=re.compile(r'iphone|ios', flags=re.IGNORECASE),
xiaomi=re.compile(r'xiaomi|mi\+5|miui', flags=re.IGNORECASE),
huawei=re.compile(r'huawei', flags=re.IGNORECASE),
# 在这里增加其它的自己想要的值
# meizhu=re.compile(r'meizhu', flags=re.IGNORECASE),
)

urls的配置 - Api

1
2
3
4
urls = [
r'x/tree-game/',
Api(r'/app/re/taskCenter/info/v1/get',params_as_all=True), ,
]

urls中可以添加2类对象: str, Api

Api类说明

1
2
class Api(object):
def __init__(self, url, f_name='', log='', params_as_all=False, p_as_all_limit=50, body_as_all=False, f_p_enc: set=None, f_b_enc: set=None, f_p_arg: set=None, f_p_kwarg: dict=None, f_b_arg: set=None, f_b_kwarg: dict=None, content_type=''):

参数说明

  • url - 不需要完整的URL
1
2
3
4
5
6
7
urls = [
# 方式01:包含host
'game-center-new.1sapp.com/x/open/game',

# 方式02:只有path
'/x/open/game',
]
  • url - 通配: 一个URL生成多个api方法代码
1
2
3
4
5
6
7
8
urls = [
# 只要写'score_task/v1/sleep/'路径前缀, 所有以它为前缀的方法都会生成相应代码
# 'score_task/v1/sleep/status/',
# 'score_task/v1/sleep/start/',
# 'score_task/v1/sleep/stop/',
# 'score_task/v1/sleep/done_task/',
'score_task/v1/sleep/',
]
  • url - 通配与匹配顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
urls = [
# 当触发'score_task/v1/sleep/done_task/'时,
# 因为'score_task/v1/sleep/'先添加并且也匹配
# 不会再寻找最佳匹配'score_task/v1/sleep/done_task/'
# 所以会生成默认的方法名:score_task_v1_sleep_done_task
# 而不是:sleep_done_task
'score_task/v1/sleep/',
Api('score_task/v1/sleep/done_task/', f_name='sleep_done_task')
]

最佳实践:先具体[长], 后模糊[前缀]

urls = [
Api('score_task/v1/sleep/done_task/', f_name='sleep_done_task')
'score_task/v1/sleep/',
]
  • f_name - 指定方法名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
默认方法名:
parse_result = urlparse(request.url)
url_path = parse_result.path
function_name = re.sub(r'[./-]', '_', url_path).strip('_').lower()

比如:
urls = ['/score_task/v1/walk/count/']
触发的url为:https://i-hl.snssdk.com/score_task/v1/walk/count/
默认:
def score_task_v1_walk_count(self)

指定后:
urls = [Api('/score_task/v1/walk/count/',f_name='walk_count')]
def walk_count(self):
  • log - 指定打印的日志信息
1
2
3
4
5
6
7
默认:
def score_task_v1_walk_count(self):
logging.info('score_task_v1_walk_count')
指定后:
urls = [Api('/score_task/v1/walk/count/',log='睡觉 - 领金币')]
def score_task_v1_walk_count(self):
logging.info('睡觉 - 领金币')
  • params_as_all\body_as_all - 应对sign签名的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
比如:
趣头条的'任务'界面,会调用
http://api.1sapp.com/app/re/taskCenter/info/v1/get
来获取任务列表\进度信息
params有一个字段:"sign": "1ad43dd2b7dbacc62b0e2b98e325483a"
这个字段和其它字段是一个整体,且我们不能伪造sign的值
直接把整个params值拿来用
默认:
def app_re_taskcenter_info_v1_get(self):
url = self.urls['app/re/taskcenter/info/v1/get']
params = self._params_from(url)
指定后:
urls = [Api(r'/app/re/taskCenter/info/v1/get',params_as_all=True)]
def app_re_taskcenter_info_v1_get(self, params_as_all):
url = self.urls['app/re/taskcenter/info/v1/get']
params = self._params_from(url)
params = params_as_all

并会生成一个全局方法
def app_re_taskcenter_info_v1_get(user: User):
for item in user.params_as_all['/app/re/taskCenter/info/v1/get']:
user.app_re_taskcenter_info_v1_get(item)
  • p_as_all_limit - 没有实现
  • f_p_arg\f_b_arg - 输入参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
默认:
def actcenter_piggy_videoconfirm(self):
params = self._params_from(url)
指定后:
urls = [Api(r'/actcenter/piggy/videoConfirm', f_p_arg={'tag','count'})]
def actcenter_piggy_videoconfirm(self, tag, count):
params = self._params_from(url)
params['tag'] = tag
params['count'] = count
```
* f_p_enc\f_b_enc - params_as_all 与 f_p_arg 的结合体

```
类中方法 与f_p_arg\f_b_arg相同
并会生成一个像params_as_all\body_as_all相似的全局方法
def v5_user_rewar_video_callback_json(self, p):
url = self.urls['/v5/user/rewar_video_callback.json']
data = self._bodys_from(url)
data['p'] = p

def v5_user_rewar_video_callback_json(user: User):
for item in user.bodys_encry['/v5/user/rewar_video_callback.json']['p']:
user.v5_user_rewar_video_callback_json(item)
  • f_p_kwarg\f_b_kwarg - 有默认值的输入参数

未实现需求

  1. 类似动态变化可伪造的值,比如时间戳,没有实现自动生成

    1
    2
    3
    def api_contains_timstamp(self):
    #这句需要自己输入
    params['ts'] = time.time()
  2. 同品牌的2台手机,无法区分出session

[TOC]

趣消除App自动化 - 成语消消乐-全自动化

目标

趣消除App自动化 - 成语消消乐-半自动化
这篇文章实现了成语消消乐的半自动化:

  • 用户点击开始一局游戏
  • 代码自动答题
  • 对代码没有找到的成语,用户自己点击成语赢得游戏

这篇文章的目的是全自动化:

  • 代码自动开始一局游戏
  • 代码自动答题
  • 对没有全部找到的,放弃这局:等待对方赢得游戏;
  • 开始下一局游戏

写在前面:

  • 自己的文章从不介绍背景知识,直接上代码;因为定位实战,非教程
  • 看了些评论,多是不明觉厉,希望你可以评主题相关的讨论或感谢
  • 这篇文章威力巨大[呵呵],所以不要做恶;不要做恶;不要做恶

不要做恶:
比如游戏有12个成语要找,但代码只答对了11个,还有1个成语4个字,共有4*3*2*1=24种排列组合,请读者不要发24个请求来找到这最后一个成语,这是楼主认为的’做恶’,也是对自动化:对没有全部找到的,放弃这局:等待对方赢得游戏做出的取舍;你可以用游戏里的认输提示

写这篇文章与代码的目的:

  • 虚荣:有读者阅读、评论
  • 金钱:赢得游戏有几分钱
  • 时间:游戏里插了很广告,跳过广告;自动化节约自己时间
  • 能力:要写能用的代码,一定要学点什么,比如学习了websocket库
  • 爱惜:自己的手机用了3年了,移植到电脑上来执行,可以让手机再战一年啊
  • 成就:代码和文章等作品;不同维度地’虐人’的快感[鄙视]

好了,希望你找到了学习的兴趣与动力,上代码

测试环境

App: 趣消除AppiOS版本、扶我起来学数学AppiOS版本
工具: python、Charles、python第三方库websocket
背景知识:python、抓包、websocket

解决

分析

成语消消乐有2个接口:

  1. https://king.hddgood.com/king_api/v1/game/join_game[http]
  2. wss://king.hddgood.com/websock_m/websock_message?uid={}&gameid={}&token={}[websocket]
  • game/join_game接口会返回websock_m/websock_message接口需要的gameidgameid每局都不同
  • uid对每个账号是固定
  • token对一次登入是固定,每局游戏都一样;
  • 游戏的消息来回传递都在websock_m/websock_message接口websocket协议里完成
1
2
3
4
5
6
POST /king_api/v1/game/join_game HTTP/1.1
Host: king.hddgood.com
A-Token-Header: PTtWUFdWUkBFHEVZCVcNdUtVWwdc=
Cookie: UM_distinctid=16b27e625da1ef-038c4847dc733-336d7451-4a640-16b27e625dd490; cn_1276022107_dplus=%7B%22distinct_id%22%3A%20%2216b27e625da1ef-038c4847dc733-336d7451-4a640-16b27e625dd490%22%2C%22%24_sessionid%22%3A%20104%2C%22%24_sessionTime%22%3A%201561087099%2C%22%24dp%22%3A%200%2C%22%24_sessionPVTime%22%3A%201561087099%2C%22initial_view_time%22%3A%20%221559738991%22%2C%22initial_referrer%22%3A%20%22%24direct%22%2C%22initial_referrer_domain%22%3A%20%22%24direct%22%2C%22%24recent_outside_referrer%22%3A%20%22%24direct%22%7D; CNZZDATA1276022107=326225286-1559738991-%7C1561086230

uid=1457362&rank=11&type=G
1
2
3
4
5
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Connection: close

{"success":true,"msg":"操作成功","code":"200","codemsg":"操作成功","result":{"gameid":"G11-810737","dup":0,"starter":472251}}

工作原理

写2个文件:chengyu-auto.py[代码文件]、chengyu.text[数据文件]

  • chengyu-auto.pyasking消息里解析出ask_stringchengyu.text文件里查找是否包含相应的成语
  • 自动提交成语答案
  • chengyu.text文件刚开始是空的;在每局游戏结束时,游戏都会发送game_result消息给我们,里面有这局游戏的答案成语,把这些成语写到文件中
  • 玩的局数越多,chengyu.text文件包含的成语越多,查找到答案的可能性越大

代码

需要安装第三方python库:websockets
chengyu-auto.py

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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
#!/usr/bin/env python3
# coding=utf-8

'''
# 趣消除App-成语消消乐全自动化;
# App版本:1.1.2
# App地址:https://itunes.apple.com/cn/app/id1449545954
提现非常迅速
'''

import re
import time
import datetime
import random
import json
import sys
import logging
import collections
import pathlib

import requests


Red = '\033[0;31m'
Green = '\033[0;32m'
Yellow = '\033[0;33m'
Blue = '\033[0;34m'
Purple = '\033[0;35m'
Cyan = '\033[0;36m'
White = '\033[0;37m'

colors = {
0:Red,
1:Purple,
2:Yellow,
3:Blue,
4:White,
}


# 这些变量的值可以通过像Charles抓包软件获得
# 账号变量
# ------------------------------------------------
# A_Token_Header的一些结论:
# 1.每个账号不同;
# 2.同一个账号每次登录时也是不一样的
# 3.同一个账号,退出时,只要不登录,上次的A-Token-Header的值还有效,只有再登录时,上次的token值才失败
A_Token_Header_zxg = 'PTtWUFdWUkBFHEVZCVcNdUtVWwdc'


# Cookie的一些结论:
# 1.同一个账号,退出或再登录,都不用修改,一直有效
# 2.值为空也可以
Cookie_zxg = ''

# UUID的一些结论:
# 1.固定不变
UUID_zxg = '1457362'
# ------------------------------------------------

api_ = 'https://king.hddgood.com/king_api/v1/'


class QuXiaoChuUser():
headers = {
'Host': 'king.hddgood.com',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-cn',
'Origin': 'https://king.hddgood.com',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16D57/; quxiaochu/ios v1.1.2',
'Referer': 'https://king.hddgood.com/'
}

data = {
'uid': '',
'channel': '',
'version': '1.1.2',
'os': 'ios',
'web_ver': '20190261'
}

SLEEP = 0.5

def __init__(self, uid, token_header, cookie):
self.uid = uid
self.headers = dict(QuXiaoChuUser.headers)
self.headers['A-Token-Header'] = token_header
self.token_header = token_header
self.headers['Cookie'] = cookie

def game_chengyu_join_game(self, rank):
'''
成语消消乐-获取游戏id
https://king.hddgood.com/king_api/v1/game/join_game
{"success":true,"msg":"操作成功","code":"200","codemsg":"操作成功","result":{"gameid":"G15-3232777","dup":0,"starter":531492}}
'''
print("成语消消乐-获取游戏id {}".format(self.uid))

data = self._uid_data()
# 1:书童;2:儒生;15:殿阁大学士
data['rank'] = str(rank)
data['type'] = 'G'

api = self._genapi('game/join_game')
result = self._post(api, self.headers, data)
return json.loads(result)

def _uid_data(self):
return {'uid': self.uid}

@staticmethod
def _genapi(path):
return 'https://king.hddgood.com/king_api/v1/' + path

@staticmethod
def _post(api, headers, data, p=logging.warning):
time.sleep(QuXiaoChuUser.SLEEP)

res = requests.post(api, headers=headers, data=data, verify=False)
print(res.url)
result = res.text
print(result)
print('')
return result


class Chengyu(object):
def __init__(self):
path = pathlib.PurePath(__file__)
path = path.parent.joinpath('chengyu.text')
self.dictpath = str(path)
self.chengyu = set()
with open(self.dictpath, 'rt') as f:
for line in f.readlines():
self.chengyu.add(line.strip())

self.answers = list()
self.ask_string = ''

# {'和':[1,8], '我':[11]}
self.char_indexs_dict = dict()

# {1:'和', 8:'和', 11:'我'}
self.index_char_dict = dict()

self.count = 0

# 自动提交答案的网络发送次数
self.auto_send_answers = list()
self.ack_true_answers = list()

# 找到的的成语中各异字符为2个的答案数量:如 [真真假假]
self.answer_2chars_count = 0

# {'中流砥柱':[1,9,21,25]}
self.answer_indexs_dict = dict()

# {'中流砥柱':set('中流砥柱')}
self.answer_charset_dict = dict()

# 查找到的错误答案
self.error_answers = []

# ---------------------------------------------
def find_answers_v2(self, ask_string):
'''
在内存成语字典查找答案
'''
ask_set = set(ask_string)
for i, c in enumerate(ask_string):
self.char_indexs_dict.setdefault(c, []).append(i)
self.index_char_dict = dict( zip(range(len(ask_string)), ask_string))

max_count = (len(ask_string) / 4 ) * 1.5
for item in self.chengyu:
item_set = self.answer_charset_dict.setdefault(item, set(item))
if not (item_set - ask_set):
self.answers.append(item)
if len(item_set)<4:
self.answer_2chars_count += 1
if len(self.answers) - self.answer_2chars_count >= max_count :
self.count = len(self.answers)
return
self.count = len(self.answers)

async def auto_answer(self, flow):
if len(self.answers):
item = self.answers[0]
answer_index = []
counter = collections.Counter(item)

for char, count in counter.items():
if self.char_indexs_dict[char]:
if len(self.char_indexs_dict[char]) < count:
self.error_answers.append(item)
self.answers.remove(item)
return
else:
pass

for c in item:
if self.char_indexs_dict[c]:
index = self.char_indexs_dict[c][0]
answer_index.append( str(index) )
del self.char_indexs_dict[c][0]
else:
pass


if len(set(answer_index)) < 4:
print('算法有错误:{} 小于4'.format(answer_index))

send_message = {
'answer': item,
'answer_index': answer_index,
'type': 'answer'
}
mm = json.dumps(send_message)
# -----------------------
print(mm)
# -----------------------
self.answer_indexs_dict[item] = answer_index
# 向服务器发送消息
self.auto_send_answers.append(item)
self.answers.remove(item)
await flow.send(mm)
# time.sleep(0.5)


def add_new_worlds_to_memory(self, m):
'''
把答案增加到内存字典中
'''
if len(self.ack_true_answers) < len(m['all_answer']):
for answer in m['all_answer']:
self.chengyu.add(answer['phrase'])

print('\033[1;31m 共收录{}个成语 \033[0m'.format(len(self.chengyu)))

def add_new_worlds_to_file(self, m):
'''
把答案增加到文件中
'''
if len(self.ack_true_answers) < len(m['all_answer']):
with open(self.dictpath, 'wt') as f:
l = list(self.chengyu)
l.sort()
for item in l:
f.write(item)
f.write('\n')

def print_answers(self):
'''
图形化、色彩化显示答案
'''
self.print_color('共找到 {}/{} 个成语'.format(self.count, len(self.ask_string)//4))
self.print_color('错误成语 {}'.format(self.error_answers))
self.print_color('共自动 {} 次提交:{}'.format(len(self.auto_send_answers),self.auto_send_answers))
self.print_color('已确认 {} 个提交:{}'.format(len(self.ack_true_answers),self.ack_true_answers))
self.print_color('问题 {}'.format(self.ask_string))
for item in self.answers:
self.print_color(item)
# self.print_matrix(item)

if (not self.answers) and self.index_char_dict:
self.print_matrix()


def print_matrix(self, item = []):
chars_in_line = 6
length = len(self.ask_string)

lines = (length + chars_in_line - 1) // chars_in_line
PADDING = ' '*(lines * chars_in_line - length)
is_need_padding = len(PADDING) != 0

self.print_color('--'*chars_in_line)

global colors, White
for i, c in self.index_char_dict.items():
end = ''
if (i+1) % chars_in_line == 0 or (i+1) == length:
end = '\n'

color = White
if c in item:
color = colors[item.index(c)]

line, first = divmod(i, chars_in_line)
if is_need_padding and first == 0 and (line + 1 == lines):
c = PADDING + c

self.print_color(c, end=end, color=color)

self.print_color('--'*chars_in_line)

def print_color(self, message, end='\n', color=Red):
print('{}{}\033[0m'.format(color, message), end=end)


def reset_data_to_init(self):
self.ask_string = ''
self.answers.clear()
self.index_char_dict.clear()

self.count = 0
self.answer_2chars_count = 0

self.answer_indexs_dict.clear()
self.char_indexs_dict.clear()
self.error_answers.clear()
self.ack_true_answers.clear()
self.auto_send_answers.clear()


def chengyu_auto_answer(user: QuXiaoChuUser):
'''
成语消消乐自动答题
wss://king.hddgood.com/websock_m/websock_message?uid=472251&gameid=G15-3232777&token=JSdLVVRRV0ZCH0INUlYNchcDUlc=
'''

result = user.game_chengyu_join_game(g_rank)
if result['success']:
gameid = result['result']['gameid']
url = 'wss://king.hddgood.com/websock_m/websock_message?uid={}&gameid={}&token={}'
url = url.format(user.uid, gameid, user.token_header)
print(url)

import asyncio
import websockets

async def chengyu():
async with websockets.connect(url) as websocket:
print('连接成功')
global chengyu
live = True
count = 0
while live:

if count % 10 == 0:
keeplive = json.dumps({"type":"keepalive"})
await websocket.send(keeplive)
print('send keeplive')

# await asyncio.sleep(0.5)
count += 1

m = await websocket.recv()
print(f"\n{m}\n")

m = json.loads(m)
message_type = m['type']
if m.get('ask_string'):
chengyu.ask_string = m['ask_string']
# 计算答案
chengyu.find_answers_v2(chengyu.ask_string)

if message_type == 'answer':
chengyu.answer_indexs_dict[m['answer']] = m['answer_index']


# 删除已回答正确的答案
if m.get('ack') == 1:

answer = m['answer']
chengyu.ack_true_answers.append(answer)
answer_index = chengyu.answer_indexs_dict.get(answer,[])
for i in answer_index:
chengyu.index_char_dict[int(i)] = ' '
try:
chengyu.answers.remove(m['answer'])
except:
pass

# 自动答题
await chengyu.auto_answer(websocket)

# 显示答案
if len(chengyu.ask_string):
chengyu.print_answers()


if message_type == 'game_result':
live = False
# 把答案增加到内存字典中
chengyu.add_new_worlds_to_memory(m)

chengyu.add_new_worlds_to_file(m)

chengyu.reset_data_to_init()


# 其它解析
for item in m['scores']:
if str(item['uid']) == user.uid:
global g_rank
g_rank = item['rank']

print('\033[1;31m 获得金币: {} Rank: {}\033[0m'.format(m['coin'], g_rank))

print('\033[1;31m 游戏结束 \033[0m')

asyncio.get_event_loop().run_until_complete(chengyu())


def genUsers():
yield QuXiaoChuUser(UUID_zxg, A_Token_Header_zxg, Cookie_zxg)

g_rank = 15
chengyu = Chengyu()

if __name__ == "__main__":

for user in genUsers():

start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
for _ in range(20):
chengyu_auto_answer(user)
time.sleep(1)
print('开始时间 ', start_time)
print('结束时间 ', time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))

chengyu.text

1
2
3
4
5
6
7
8
9
一劳永逸
一掷千金
一曝十寒
一石二鸟
一筹莫展
一落千丈
一衣带水
一语破的
...

注意:
chengyu.textchengyu-auto.py放在同一目录下
chengyu.text收集约1926个成语,98%能找到全部答案

参考

楼主的趣消除App系列文章

  1. 趣消除App自动化 - 签到and作战休息区
  2. 趣消除App自动化 - 成语消消乐-半自动化
  3. 趣消除App自动化 - 成语消消乐-全自动化