使用 shell 脚本自动申请进京证 (六环外) —— debug 过程 ...

打印 上一主题 下一主题

主题 528|帖子 528|积分 1584

问题现象

用 shell 脚本写了一个自动办理六环外进京证的工具 《使用 shell 脚本自动申请进京证 (六环外)》,然而运行这个脚本总是返回以下错误信息:
  1. {
  2.   "msg": "目前办理业务人数较多,请稍后再试。",
  3.   "code": 500
  4. }
复制代码
咨询 woodheader/jjz 项目的作者,了解到问题就是出在请求头或参数上。仔细的检查了传入的各种参数,没有发现任何问题;修改 http 头的格式 (key 与 value 间增加空格),也没有丝毫改善。
写脚本花了两天,调试脚本花了三天却还没摸到门径,真是见了鬼了。有时候怀疑是自己被拉进反作弊名单了,切换另外一台设备的 source 和 authorization 后,结果还是出错,真是离了大谱。
思路

目前在 android 设备的 App 上进行请求用同样的参数是可以办理成功的,并且有 VNET/Charles 的抓包数据。如果能对脚本的请求进行抓包,再将两者对比起来看,问题就容易暴露了。
Charles 抓包 curl


Charles 抓包的教程网上比较多,这里就不赘述了,需要注意的是和 VNET 一样,App 登录阶段不能抓包,否则登录界面调不出来。
Charles 可以抓 App 的报文,如果也能抓脚本的报文,两个一对比问题就水落石出啦~
经过一番百度,发现要让 Charles 抓命令行的报文还比较麻烦,需要配置两个环境变量:
  1. export http_proxy=172.21.222.149:8888
  2. export https_proxy=172.21.222.149:8888
复制代码
其中 172.21.222.149:8888 就是 Charles 开启代理的 IP 和端口:

然而开启抓包后,curl 要么失败,要么卡住,总是抓不到包:
  1. > sh jinjing.sh
  2. check jq ok
  3. check curl ok
  4. check head ok
  5. check cat ok
  6. check awk ok
  7. check grep ok
  8. check date ok
  9. state req: {"v":"3.4.1","sfzmhm":"150121198603226428","s-source":"bjjj-android","timestamp":"1677810190000"}
  10. state headers:  -H Accept-Language:zh-CN,zh;q=0.8 -H User-Agent:okhttp-okgo/jeasonlzy -H source:8724a2428c3f47358741f978fd082810 -H authorization:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -H Content-Type:application/json;charset=utf-8 -H Host:jjz.jtgl.beijing.gov.cn -H Connection:Keep-Alive -H Accept-Encoding:gzip -H Content-Length:0
  11. jinjing.sh: line 99: [: too many arguments
  12. query permits status ok:
  13. id [] from user token does not match given [150121198603226428], fatal error!
复制代码
VNET 抓包 curl

Charles 不行就想到了 VNET,不过它只能在 android 设备上抓包,如何让它抓 pc 上运行的 curl 呢?其实不难,把脚本放在设备上运行就好了。
adb shell 运行脚本

这个脚本只要有 shell 环境就能用,可移植性比较好,立马用 adb shell 发送到设备上试试:
  1. > adb push jinjing.sh /sdcard/
  2. jinjing.sh: 1 file pushed, 0 skipped. 9.9 MB/s (11790 bytes in 0.001s)
  3. > adb push config.ini /sdcard/
  4. config.ini: 1 file pushed, 0 skipped. 0.2 MB/s (428 bytes in 0.002s)
  5. > adb shell
  6. PD1981:/ > cd /sdcard
  7. PD1981:/sdcard > mkdir jjz
  8. PD1981:/sdcard > mv jinjing.sh config.ini jjz/                                                                                                                                       
  9. PD1981:/sdcard > cd jjz
  10. PD1981:/sdcard/jjz > ls -lh
  11. total 16K
  12. -rw-rw---- 1 root everybody 428 2023-02-10 16:01 config.ini
  13. -rw-rw---- 1 root everybody 12K 2023-03-03 10:34 jinjing.sh
  14. PD1981:/sdcard/jjz > sh jinjing.sh
  15. jinjing.sh[1]:  #!: inaccessible or not found
  16. please install jq before run this script, fatal error!
复制代码
提示没有检测到 jq,这命令确实不是 android 标配,在 pc 上都需要安装,更不要说这种移动设备了。
arm jq

翻开 jq 官网下载页,各种预编译版本中没有 arm 平台的:

通过包管理器直接安装更是想都不要想。直接下载 linux 通用版本,无论是 32 位还是 64 位都不能执行:
  1. > ./jq
  2. /system/bin/sh: ./jq: not executable: 32-bit ELF file
  3. > ./jq
  4. /system/bin/sh: ./jq: not executable: 64-bit ELF file
复制代码
网上搜索了一下,找到一个 aarch64 平台的 rpm 包:jq-1.5-1.el7.aarch64.rpm
aarch64 应该就是 arm64 没错了,经过 cpio 解压后得到了里面的 jq 可执行文件:
  1. > wget  https://download-ib01.fedoraproject.org/pub/epel/7/aarch64/Packages/j/jq-1.5-1.el7.aarch64.rpm
  2. > rpm2cpio jq-1.5-1.el7.aarch64.rpm | cpio -div
  3. ./usr/bin/jq
  4. ./usr/lib64/libjq.so.1
  5. ./usr/lib64/libjq.so.1.0.4
  6. ./usr/share/doc/jq/AUTHORS
  7. ./usr/share/doc/jq/COPYING
  8. ./usr/share/doc/jq/README
  9. ./usr/share/doc/jq/README.md
  10. ./usr/share/man/man1/jq.1.gz
  11. 873 blocks
复制代码
将 ./usr/bin/jq push 到设备执行,还是不行:
  1. PD1981:/data/local/tmp/jjz > file jq
  2. jq: ELF executable, 64-bit LSB arm64, dynamic (/lib/ld-linux-aarch64.so.1), BuildID=77ce7804044f5185df2d25650051ced0ea6bed94, stripped
  3. PD1981:/data/local/tmp/jjz > ./jq --versoin
  4. /system/bin/sh: ./jq: No such file or directory
复制代码
这里还有一个小插曲,在默认的 /sdcard 目录执行可执行文件时失败:
  1. > ./jq
  2. /system/bin/sh: ./jq: can't execute: Permission denied
复制代码
即使给了 jq 可执行权限也不行 (chmod),经过一番百度,发现需要放在另一个目录才可以:/data/local/tmp
交叉编译 jq

好在 jq 是开源的,可以直接基于源码编译,不过 android 设备上可没有编译环境,所以还得借助 linux 进行交叉编译。
  1. #! /bin/sh
  2. git clone  https://github.com/stedolan/jq.git
  3. cd jq
  4. # git submodule update --init
  5. # autoreconf will fail with following error:
  6. # ...
  7. # configure.ac:6: error: require Automake 1.14, but have 1.13.4
  8. # autoreconf: automake failed with exit status: 1
  9. mkdir build
  10. cd build
  11. autoreconf -i ..
  12. CPATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/"
  13. ../configure --without-oniguruma --disable-maintainer-mode  CFLAGS='-std=c99' --prefix=$PWD/install/ --host='armv7a-linux-androideabi21' CC="$CPATH/armv7a-linux-androideabi21-clang"  LD="$CPATH/arm-linux-androideabi-ld"  AR="$CPATH/arm-linux-androideabi-ar"
  14. make
  15. make install
复制代码
参考网上的一篇文章改了改,用上面这个脚本可以编译,前提是要有 android ndk 并将根目录设置到环境变量 ANDROID_NDK_HOME。
另外有两个小点需要注意:

  • 不要下载 jq 库中的模块 (submodule),否则 autoreconf 需要更高的版本,在我的环境中会报错退出。下载模块主要目的是为了编译 oniguruma 正则匹配库,而我们是忽略这个库的,所以没必要
  • 个人习惯创建临时目录 (build) 进行编译,方便后期清理编译产物,然而在 jq 这里却遇到了麻烦,需要稍微做一点工作
第一个问题的报错信息:
  1. > autoreconf -i .
  2. libtoolize: putting auxiliary files in `.'.
  3. libtoolize: copying file `./ltmain.sh'
  4. libtoolize: putting macros in AC_CONFIG_MACRO_DIR, `m4'.
  5. libtoolize: copying file `m4/libtool.m4'
  6. libtoolize: copying file `m4/ltoptions.m4'
  7. libtoolize: copying file `m4/ltsugar.m4'
  8. libtoolize: copying file `m4/ltversion.m4'
  9. libtoolize: copying file `m4/lt~obsolete.m4'
  10. configure.ac:6: error: require Automake 1.14, but have 1.13.4
  11. autoreconf: automake failed with exit status: 1
复制代码
这里我的 automake 刚好是 1.13.4 < 1.14,如果你的机器上版本大于等于 1.14,随便造。。
第二个问题的报错信息:
  1. > make
  2. ...
  3.   CC       src/lexer.lo
  4.   YACC     src/parser.c
  5. NOT building parser.c!
  6.   CC       src/parser.lo
  7. clang: error: no such file or directory: 'src/parser.c'
  8. clang: error: no input files
  9. make[2]: *** [src/parser.lo] Error 1
  10. make[2]: Leaving directory `/home/users/yunhai01/test/jq/build'
  11. make[1]: *** [all-recursive] Error 1
  12. make[1]: Leaving directory `/home/users/yunhai01/test/jq/build'
  13. make: *** [all] Error 2
  14. make  install-recursive
  15. make[1]: Entering directory `/home/users/yunhai01/test/jq/build'
  16. make[2]: Entering directory `/home/users/yunhai01/test/jq/build'
  17.   YACC     src/parser.c
  18. NOT building parser.c!
  19.   CC       src/parser.lo
  20. clang: error: no such file or directory: 'src/parser.c'
  21. clang: error: no input files
  22. make[2]: *** [src/parser.lo] Error 1
  23. make[2]: Leaving directory `/home/users/yunhai01/test/jq/build'
  24. make[1]: *** [install-recursive] Error 1
  25. make[1]: Leaving directory `/home/users/yunhai01/test/jq/build'
  26. make: *** [install] Error 2
复制代码
这里报 src/parser.c 找不到,然而在上一级目录中对应的位置却是有的,应该是 yacc 生成 .c 文件时放在了上一级目录,而使用 build 目录后 make 没有找到该文件,手动复制一下即可:
  1. > cp ../src/parser.c src/
  2. > make
  3. make  all-recursive
  4. make[1]: Entering directory `/home/users/yunhai01/test/jq/build'
  5. make[2]: Entering directory `/home/users/yunhai01/test/jq/build'
  6.   CC       src/parser.lo
  7.   CCLD     libjq.la
  8.   CC       src/main.o
  9.   CCLD     jq
  10. make[2]: Leaving directory `/home/users/yunhai01/test/jq/build'
  11. make[1]: Leaving directory `/home/users/yunhai01/test/jq/build'
复制代码
应该是 jq 的一个小 bug,已提交 issue
  1. > file install/bin/jq
  2. jq: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped
  3. > adb push install/bin/jq /data/local/tmp/jjz/
  4. > adb shell
  5. PD1981:/ > cd /data/local/tmp/jjz
  6. PD1981:/data/local/tmp/jjz > chmod u+x jq
  7. PD1981:/data/local/tmp/jjz > export PATH="$PATH:$PWD"
  8. PD1981:/data/local/tmp/jjz > jq --version
  9. jq-1.6-159-gcff5336-dirty
复制代码
push 到设备就能用啦,这里为了脚本调用方便设置了 PATH 环境变量,断开 adb 后就失效了,需要每次登录都设置一下。
shell 数组初始化

有了 jq 就可以继续开开心心地跑脚本了,然而得到当头一棒:
  1. > sh jinjing.sh
  2. check jq ok
  3. check curl ok
  4. check head ok
  5. check cat ok
  6. check awk ok
  7. check grep ok
  8. check date ok
  9. jinjing.sh[71]: syntax error: unexpected '('
复制代码
看看 71 行的内容:
  1.     local stateheader=()
复制代码
再普通不过的 shell 数组初始化语法,看起来非 bash 的 shell 不认,只好把它改成更通用的形式:
  1.     local stateheader
复制代码
这丝毫不影响数组的初始化。
硬编码日期

继续运行脚本,这回跑通了,然而申请结果不正确:
  1. {
  2.   "code": 500,
  3.   "msg": "进京日期不能为空!",
  4.   "data": null,
  5.   "from": "v2"
  6. }
复制代码
再看设置的申请日期:
  1. in effect from 2023-02-25 to 2023-03-03
  2. date: bad date +1 days
  3. new permit will start from
复制代码
居然是空。原来是 adb shell 中的 date 不支持 "+1 days" 这种 unix date 语法:
  1. PD1981:/data/local/tmp/jjz > date '+%Y-%m-%d' -d "+1 days"                                                                                                              
  2. date: bad date +1 days
  3. PD1981:/data/local/tmp/jjz > date -v+1d "+%Y-%m-%d"                                                                                                                 
  4. date: Unknown option 'v+1d' (see "date --help")
复制代码
mac date 那种 "-v+1d" 也不支持,看 adb date 的说明:
  1. $ date --help
  2. Toybox 0.8.4-android multicall binary: https://landley.net/toybox (see toybox --help)
  3. usage: date [-u] [-I RES] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]
  4. Set/get the current date/time. With no SET shows the current date.
  5. -d        Show DATE instead of current time (convert date format)
  6. -D        +FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])
  7. -I RES        ISO 8601 with RESolution d=date/h=hours/m=minutes/s=seconds/n=ns
  8. -r        Use modification time of FILE instead of current date
  9. -u        Use UTC instead of current timezone
  10. Supported input formats:
  11. MMDDhhmm[[CC]YY][.ss]     POSIX
  12. @UNIXTIME[.FRACTION]      seconds since midnight 1970-01-01
  13. YYYY-MM-DD [hh:mm[:ss]]   ISO 8601
  14. hh:mm[:ss]                24-hour time today
  15. All input formats can be followed by fractional seconds, and/or a UTC
  16. offset such as -0800.
  17. All input formats can be preceded by TZ="id" to set the input time zone
  18. separately from the output time zone. Otherwise $TZ sets both.
  19. +FORMAT specifies display format string using strftime(3) syntax:
  20. %% literal %             %n newline              %t tab
  21. %S seconds (00-60)       %M minute (00-59)       %m month (01-12)
  22. %H hour (0-23)           %I hour (01-12)         %p AM/PM
  23. %y short year (00-99)    %Y year                 %C century
  24. %a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)
  25. %b short month name      %B month name           %Z timezone name
  26. %j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)
  27. %N nanosec (output only)
  28. %U Week of year (0-53 start Sunday)   %W Week of year (0-53 start Monday)
  29. %V Week of year (1-53 start Monday, week < 4 days not part of this year)
  30. %F "%Y-%m-%d"   %R "%H:%M"        %T "%H:%M:%S"        %z  timezone (-0800)
  31. %D "%m/%d/%y"   %r "%I:%M:%S %p"  %h "%b"              %:z timezone (-08:00)
  32. %x locale date  %X locale time    %c locale date/time  %s  unix epoch time
复制代码
也没看出来个所以然。搞不懂这个 adb date 了,好在只是调试,可以直接硬编码日期为一个合法值:
  1. # mac date performs differs with other unix..
  2. if [ ${IS_MAC} -eq 1 ]; then
  3.     issuedate=$(date "-v+${expire}d" '+%Y-%m-%d')
  4. else
  5.     issuedate=$(date '+%Y-%m-%d' -d "+${expire} days")
  6. fi
  7. issuedate="2023-03-04"
复制代码
然后就成功了!
  1. {
  2.   "code": 200,
  3.   "msg": "信息已提交,正在审核!",
  4.   "data": [
  5.     "温馨提示",
  6.     "1、请务必在进京之前,查看进京通行证是否审核通过;",
  7.     "2、若审核未通过,请按提示信息调整并重新申请;",
  8.     "3、若审核通过,可在证件生效之前申请取消,每位注册用户每天仅有1次取消机会;",
  9.     "4、在进京通行证未生效的情况下,外埠机动车禁止在限行区域内行驶;"
  10.   ],
  11.   "from": "v2"
  12. }
复制代码
VNET

本来要抓出错的报文进行对比,没想到 adb shell 上居然歪打正着跑通了,这下 VNET 抓包也没什么用了,虽然通过指定 shell 可以实现抓包:



可以看到,http 头中的 Key 名称重复了,应该是 VNET 显示的问题。另外对比 Charles 与 VNET 的抓包结果,发现以下字段是 VNET 自己加的:

  • ip: 203.34.106.199
  • type: POST
  • time: 2023-03-03 15:48:51
  • size: 705
  • code: 200
可能是为了显示方便,在脚本里加这些参数纯粹是画蛇添足,所以我都删掉了。
这里有一个小插曲,如果不打开 curl 的 -k 参数,VNET 抓包会导致 curl 请求卡死,且 VNET 也抓不到包,这是通过 -v 选项发现的:
  1. curl: (60) SSL certificate problem: self signed certificate in certificate chain
  2. More details here: https://curl.haxx.se/docs/sslcerts.html
  3. curl failed to verify the legitimacy of the server and therefore could not
  4. establish a secure connection to it. To learn more about this situation and
  5. how to fix it, please visit the web page mentioned above.
复制代码
升级 curl

走到这一步就很有意思了:pc 上 curl 失败、android 上成功;pc 上能抓 App 包、抓不到 curl 包;android 上能抓两者的包但是都成功没有对比意义。
现在能直接对比的只有 pc 上的 curl 和 android 的上 curl,于是对比了一下两者的版本:
  1. macOS:
  2. > curl -V
  3. curl 7.64.1 (x86_64-apple-darwin20.0) libcurl/7.64.1 (SecureTransport) LibreSSL/2.8.3 zlib/1.2.11 nghttp2/1.41.0
  4. Release-Date: 2019-03-27
  5. Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
  6. Features: AsynchDNS GSS-API HTTP2 HTTPS-proxy IPv6 Kerberos Largefile libz MultiSSL NTLM NTLM_WB SPNEGO SSL UnixSockets
  7. CentOS:
  8. > curl -V
  9. curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.53.1 zlib/1.2.7 libidn/1.28 libssh2/1.8.0
  10. Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smtp smtps telnet tftp
  11. Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz unix-sockets
  12. android:
  13. > curl -V
  14. curl 7.73.0 (Android) libcurl/7.73.0 BoringSSL zlib/1.2.11
  15. Release-Date: 2020-10-14
  16. Protocols: file http https mqtt
  17. Features: AsynchDNS HTTPS-proxy IPv6 libz NTLM SSL UnixSockets
复制代码
其实是三者,pc 有两个:一个 linux,一个 mac。发现它们版本都不尽相同,不过 android 上的版本比 pc 的都大,可以考虑升级 linux 的版本到 7.87 尝试。
这里没有再用源码编译安装的方式,直接下载一个 linux x86 版本完事:
  1. > bin/curl -V
  2. curl 7.87.0 (x86_64-pc-linux-muslx32) libcurl/7.87.0 OpenSSL/1.1.1s zlib/1.2.12 libssh2/1.9.0 nghttp2/1.43.0
  3. Release-Date: 2022-12-21
  4. Protocols: dict file ftp ftps gopher gophers http https imap imaps mqtt pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
  5. Features: alt-svc AsynchDNS HSTS HTTP2 HTTPS-proxy IPv6 Largefile libz NTLM NTLM_WB SSL threadsafe TLS-SRP UnixSockets
  6. > pwd
  7. /home/users/yunhai01/tools
  8. > echo $PATH
  9. /home/yunh/.BCloud/bin:/home/users/yunhai01/.local/bin:/home/users/yunhai01/bin:/home/users/yunhai01/tools/bin:/home/users/yunhai01/project/android-ndk-r20:/usr/lib64/qt-3.3/bin:/usr/local/bin:/usr/bin:/opt/bin:/home/opt/bin:/home/users/yunhai01/tools/node-v14.17.0-linux-x64/bin
  10. > curl -V
  11. curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.53.1 zlib/1.2.7 libidn/1.28 libssh2/1.8.0
  12. Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smtp smtps telnet tftp
  13. Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz unix-sockets
  14. > type curl
  15. curl is hashed (/usr/bin/curl)
  16. > whereis curl
  17. curl: /usr/bin/curl /home/users/yunhai01/tools/bin/curl /usr/share/man/man1/curl.1.gz
复制代码
这里有一个小插曲,即使我将新下载的 curl 所在的路径 (tools/bin) 放在了 PATH 环境变量当中,访问 curl 时仍是访问系统自带的那个,只得将脚本中所有 curl 通过指定全路径的方式来切换为新版。
最后在 linux 上执行脚本仍失败。
对比 curl 输出

走到这儿我是真的郁闷了。既然不能抓包,那就对比 curl -v 输出吧!索性脚本已经能在 android 上跑通了,有个可以对比的基准了:

过滤掉一些版本的差异,发现了最重要的区别:Content-Length,android 上的长度是 340,而 pc 上只有 304。长度不足会导致 post 数据被截断,服务器返回 500,这就说通了。
那为何相同的请求数据会得到不同的长度呢?先看看请求数据到底是多长:
  1. > echo ${issue_req}
  2. {"dabh":"null","hphm":"津ADY1951","hpzl":"52","vId":"1479816562371952600","jjdq":"海淀区","jjlk":"00401","jjlkmc":"京藏高速","jjmd":"01","jjmdmc":"自驾旅游","jjrq":"2023-03-04","jjzzl":"02","jsrxm":"云海","jszh":"150121198603226428","sfzmhm":"150121198603226428","xxdz":"百度大厦","sqdzbdjd":116.307393,"sqdzbdwd":40.057771}
  3. $ echo ${issue_req} | wc -c
  4.      341
  5. $ echo ${#issue_req}
  6. 304
复制代码
果然是请求成功的 340,其中多了个 1 是结尾换行。而脚本中指定的 Content-Length 是通过 shell 字符串长度获取的 (${#issue_req}),这个在 pc 上果然是 304。
所以问题的根因就清楚了,是错误的将 shell 字符串长度做为了数据长度,当数据内容中不包含汉字时,它俩是一致的,这也是为什么 stateList 可以请求成功的原因;而当数据中包含 utf-8 汉字后,一个汉字占用 3 个字节,在 shell 字符串中却只统计了一次,所以导致长度偏小。
这正是 —— 踏破铁鞋无觅处,得来全不费功夫啊!明明感觉只隔了一层窗户纸,没想到捅破它却用尽了浑身的力气,哈哈~
痛定思痛,不要使用 shell 字符串长度作为数据长度就是这个 bug 的经验教训。
复盘

最后来复盘一下,为何 adb shell 中包含汉字的字符串长度就能等于数据长度呢?下面做个小实验:
  1. > data='{"dabh":"null" "hphm":"津ADY1951" "hpzl":"52" "vId":"1479816562371952600" "jjdq":"海淀区" "jjlk":"00401" "jjlkmc":"京藏高速" "jjmd":"01" "jjmdmc":"自驾旅游" "jjrq":"2023-03-04" "jjzzl":"02" "jsrxm":"云海" "jszh":"150121198603226428" "sfzmhm":"150121198603226428" "xxdz":"百度大厦" "sqdzbdjd":116.307393 "sqdzbdwd":40.057771}'
  2. > echo "${data}"
  3. {"dabh":"null" "hphm":"津ADY1951" "hpzl":"52" "vId":"1479816562371952600" "jjdq":"海淀区" "jjlk":"00401" "jjlkmc":"京藏高速" "jjmd":"01" "jjmdmc":"自驾旅游" "jjrq":"2023-03-04" "jjzzl":"02" "jsrxm":"云海" "jszh":"150121198603226428" "sfzmhm":"150121198603226428" "xxdz":"百度大厦" "sqdzbdjd":116.307393 "sqdzbdwd":40.057771}
  4. > echo ${#data}
  5. 304
  6. > cat test.sh
  7. #! /bin/sh
  8. data='{"dabh":"null" "hphm":"津ADY1951" "hpzl":"52" "vId":"1479816562371952600" "jjdq":"海淀区" "jjlk":"00401" "jjlkmc":"京藏高速" "jjmd":"01" "jjmdmc":"自驾旅游" "jjrq":"2023-03-04" "jjzzl":"02" "jsrxm":"云海" "jszh":"150121198603226428" "sfzmhm":"150121198603226428" "xxdz":"百度大厦" "sqdzbdjd":116.307393 "sqdzbdwd":40.057771}'
  9. echo ${#data}
  10. echo "${data}" | wc -c
  11. > sh test.sh
  12. 340
  13. 341
复制代码
发现两个有趣的现象:

  • 直接将数据赋给 adb shell 变量时,长度是 304 短缺 (注意如果不将 data 用双引号括住,json 数据的外花括号将缺失,不清楚为何)
  • 调用 shell 脚本赋值给 shell 变量时,长度为 340 正常,与 wc 的输出仅差了一个换行,可以看作是一致的
adb shell 在交互执行和脚本执行时行为还不一样,这真是离大谱。感兴趣的读者可以进一步探究,我是查不动了。。
结语

本文记录了一个脚本不工作的排查过程,在尝试抓包进行报文对比思路的引导下,分别探索了 Charles pc 抓 curl -> VNET android 抓 curl -> jq arm 交叉编译 -> 去除 shell 数组初始化 -> 去除 date +1 -> 升级 curl -> 对比 pc 和 android 上的 curl -v 输出,最终定位到了问题根因:使用 shell 字符串长度作为数据长度、在操作 utf-8 汉字数据时计算了错误的 Content-Length、从而引发了服务端返回错误响应的过程。
bug 没什么神奇的,甚至有点低级,说出来还有点不好意思。不过探索问题的过程就是这样,不到最后一刻,永远不知道自己被多么小的错误绊倒了。虽然错误低级,排查的过程还是蛮高大上的,总体思路也是正确的,只是在具体的摸索过程中走了不少弯路,回头来看看,也蛮有意思,特别是 android adb shell,真的对它产生了新的认知。
adb shell 拓展了 shell 脚本运行的平台,之前写的好多脚本,其实都可以称到 android 设备上跑。这方面有一个 Termux 可用,如果再和定时执行联系起来,大有可为,一机在手走遍天下,这样看 linux 服务器都可以省了,哈哈~
后记

在写这篇文章的时候,又对上述流程做了个梳理,补充两个新的情况。
arm jq

正文使用的是 rpm 包,我在搜索时又找到一个 deb 包:jq_1.6-1ubuntu0.20.04.1_arm64.deb
  1. > wget  http://ports.ubuntu.com/pool/universe/j/jq/jq_1.6-1ubuntu0.20.04.1_arm64.deb
  2. > ar -vx jq_1.6-1ubuntu0.20.04.1_arm64.deb
  3. x - debian-binary
  4. x - control.tar.xz
  5. x - data.tar.xz
  6. > tar xvf data.tar.xz
  7. x ./
  8. x ./usr/
  9. x ./usr/bin/
  10. x ./usr/bin/jq
  11. x ./usr/share/
  12. x ./usr/share/doc/
  13. x ./usr/share/doc/jq/
  14. x ./usr/share/doc/jq/AUTHORS.gz
  15. x ./usr/share/doc/jq/copyright
  16. x ./usr/share/man/
  17. x ./usr/share/man/man1/
  18. x ./usr/share/man/man1/jq.1.gz
  19. x ./usr/share/doc/jq/README
  20. x ./usr/share/doc/jq/changelog.Debian.gz
复制代码
将它解压并推送到设备上,还是没效果,看来还必需走交叉编译这步了。
Charles 抓包 curl

设置 http_proxy/https_proxy 环境变量后,启动脚本,Charles 抓到 curl 的包了:

curl 也正常返回了,之前卡死或失败的场景不再复现了,神奇。
如果当时这一步走通的话,就可以直接对比 curl 的报文与 App 的报文找到原因了!而不用绕这么大一圈,Charles 坑我~
后来回想一下,可能是为 curl 增加了 -k 选项的缘故。
参考

[1]. Linux bash终端设置代理(proxy)访问
[2]. Download jq
[3]. rpm文件解压
[4]. .deb文件的解压与压缩
[5]. how-to-set-executable-permissions-on-a-file-in-android-to-execute-using-runtime
[6]. jq的交叉编译
[7]. Shell curl 命令报错:(60) SSL certificate problem: self signed certificate
[8]. curl download
[9]. Linux shell统计字节数、字数、行数

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

河曲智叟

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表