virjar/sekiro: 基于长链接和代码注入的Android private API暴露框架
2020-07-16 22:20:06 Author: github.com(查看原文) 阅读量:739 收藏

SEKIRO 是一个 android 下的 API 服务暴露框架,可以用在 app 逆向、app 数据抓取、android 群控等场景。

Sekiro 是我之前设计的群控系统 Hermes 的升级版,和其他群控框架相比的特点如下:

  1. 对网络环境要求低,sekiro 使用长链接管理服务,使得 Android 手机可以分布于全国各地,甚至全球各地。手机掺合在普通用户群体,方便实现反抓突破,更加适合获取下沉数据。
  2. 不依赖 hook 框架,就曾经的 Hermes 系统来说,和 xposed 框架深度集成,在当今 hook 框架遍地开花的环境下,框架无法方便迁移。所以在 Sekiro 的设计中,只提供了 RPC 功能了。
  3. 纯异步调用,在 Hermes 和其他曾经出现过的框架中,基本都是同步调用。虽然说签名计算可以达到上百 QPS,但是如果用来做业务方法调用的话,由于调用过程穿透到目标 app 的服务器,会有大量请求占用线程。系统吞吐存在上线(hermes 系统达到 2000QPS 的时候,基本无法横向扩容和性能优化了)。但是 Sekiro 全程使用 NIO,理论上其吞吐可以把资源占满。
  4. client 实时状态,在 Hermes 系统我使用 http 进行调用转发,通过手机上报心跳感知手机存活状态。心跳时间至少 20s,这导致服务器调度层面对手机在线状态感知不及时,请求过大的时候大量转发调用由于 client 掉线 timeout。在 Sekiro 长链接管理下,手机掉线可以实时感知。不再出现由于框架层面机制导致 timeout

部署区分服务器端部署和客户端部署,服务器使用 SpringBoot 实现,占三个端口(server.port: http管理端,同步http| natServerPort:手机nat穿透端,和手机长链接| natHttpServerPort:NIO的http服务端,只提供RPC调用入口) 手机端一般附加在 apk 代码逻辑中。

服务端部署

两种方式,基于源码部署和 jar 包运行

源码部署服务器

执行脚本 ./runProd.sh

jar 包部署

  1. 当前目录执行代码: ./gradlew sekiro-server:bootJar 即可在 sekiro-server/build/libs/sekiro-server-0.0.1-SNAPSHOT.jar找到 all-in-one 的 jar 包
  2. 通过命令 nohup java -jar sekiro-server/build/libs/sekiro-server-0.0.1-SNAPSHOT.jar >/dev/null 2>&1 & 即可启动服务器

docker 部署

Dockerfile

# 直接运行, master分支代码aliyun自动构建
docker run --restart=always --name sekiro-server -p 5600:5600 -p 5601:5601 -p 5602:5602 -p 5603:5603 -d registry.cn-beijing.aliyuncs.com/virjar/sekiro-server:latest

# 或者获取源码构建自行构建

git clone https://github.com/virjar/sekiro.git;
cd sekiro;
docker build . -t sekiro-server:latest;
docker run --restart=always --name sekiro-server -p 5600:5600 -p 5601:5601 -p 5602:5602 -p 5603:5603 -d sekiro-server:latest;


端口配置

sekiro-server/src/main/resources/appliation.properties中可以配置三个服务端端口

client 使用

需要注意,client api 发布在 maven 仓库,而非 jcenter 仓库

dependencies {
    implementation 'com.virjar:sekiro-api:1.0.2'
}

然后即可在 apk 代码中书写调用服务逻辑:

SekiroClient.start("sekiro.virjar.com",clientId,"sekiro-demo")
        .registerHandler("clientTime",new SekiroRequestHandler(){
            @Override
            public void handleRequest(SekiroRequest sekiroRequest,SekiroResponse sekiroResponse){
                    sekiroResponse.success(" now:"+System.currentTimeMillis()+ " your param1:" + sekiroRequest.getString("param1"));
            }
        });

安装 apk 到手机,并打开,然后可以通过服务器访问这个接口

http://sekiro.virjar.com/groupList

{"status":0,"message":null,"data":["sekiro-demo"],"clientId":null,"ok":true}


http://sekiro.virjar.com/natChannelStatus?group=sekiro-demo
{"status":0,"message":null,"data":["2e77bbfa_869941041217576"],"clientId":null,"ok":true}


http://sekiro.virjar.com/invoke?group=sekiro-demo&action=clientTime&param1=%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%82%E6%95%B0

{"clientId":"2e77bbfa_869941041217576","data":"process: com.virjar.sekiro.demoapp : now:1570546873170 your param1:自定义参数","ok":true,"status":0}

http://sekiro.virjar.com/asyncInvoke?group=sekiro-demo&action=clientTime&param1=%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%82%E6%95%B0

{"clientId":"2e77bbfa_869941041217576","data":"process: com.virjar.sekiro.demoapp : now:1570897005965 your param1:自定义参数","ok":true,"status":0}

client demo 在app-demo子工程可以看到,直接运行 app-demo,即可在 sekiro.virjar.com 看到你的设备列表

Sekiro 本身不提供代码注入功能,不过 Sekiro 一般需要和代码注入框架配合产生作用,如和 Xposed 配合,可以方便调用 app 内部私有 API,一般情况下,在 Xposed 入口启动 Sekiro,然后接受服务器指令,并将参数转发到 app 内部。

Sekiro 调用真实 apk 的例子: 为避免风险,现已经移除 Demo

sekiro 框架在 http 服务模块,提供了两个 http 端口,分别为 BIO 和 NIO 模式,其中 BIO 模式提供给 tomcat 容器使用,为了方便 springBoot 集成。另一方面,NIO 提供给调用转发模块,NIO 转发过程并不会占用线程池资源,理论上只对连接句柄和 CPU 资源存在瓶颈。

sekiro 的这两个服务分别占用两个不同端口,分别为:

#tomcat 占用端口
server.port=5602
#长链接服务占用端口
natServerPort=5600
# 异步http占用端口
natHttpServerPort=5601

同时两个请求的 uri 也有一点差异,分别为,

BIO: http://sekiro.virjar.com/invoke?group=sekiro-demo&action=clientTime&param1=%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%82%E6%95%B0

NIO: http://sekiro.virjar.com/asyncInvoke?group=sekiro-demo&action=clientTime&param1=%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%82%E6%95%B0

可以看到 sekiro 的 demo 网站中,都是占用了统一个端口,这是因为存在 ngnix 转发,你可以参照如下配置实现这个效果:

upstream sekiro_server {
  server 127.0.0.1:5602;
}

upstream sekiro_nio {
  server 127.0.0.1:5601;
}

server {
  listen 0.0.0.0:80;
  listen [::]:80;
  server_name sekiro.virjar.com;
  server_tokens off;

  real_ip_header X-Real-IP;
  real_ip_recursive off;


location / {
    client_max_body_size 0;
    gzip off;

    proxy_read_timeout      300;
    proxy_connect_timeout   300;
    proxy_redirect          off;

    proxy_http_version 1.1;

    proxy_set_header    Host                $http_host;
    proxy_set_header    X-Real-IP           $remote_addr;
    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_set_header    X-Forwarded-Proto   $scheme;

    proxy_pass http://sekiro_server;
  }

location /asyncInvoke {
    client_max_body_size 0;
    gzip off;

    proxy_read_timeout      300;
    proxy_connect_timeout   300;
    proxy_redirect          off;

    proxy_http_version 1.1;

    proxy_set_header    Host                $http_host;
    proxy_set_header    X-Real-IP           $remote_addr;
    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_set_header    X-Forwarded-Proto   $scheme;

    proxy_pass http://sekiro_nio;
  }
}

强烈建议使用 NIO 接口访问调用服务

基本概念解释

在 Sekiro 定义中,有几个特殊概念用来描述手机或者业务类型。了解如何使用 Sekiro 之前需要知道这几个概念的含义。

group

group 的作用是区分接口类型,如在 sekiro 系统中,存在多个不同 app 的服务,那么不同服务通过 group 区分。或者同一个 app 的服务也可能存在差异,如登陆和未登陆分不同接口组,签名计算和 数据请求调用区分不同接口组。Sekiro 不限定 group 具体含义,仅要求 group 作为一个唯一字符串,使用方需要自行保证各自 group 下是的手机业务唯一性。

备注:曾经的 Hermes 系统中,接口组定义为 app 的 packageName,导致同一个 app 的需求,无法进行二次路由,签名计算和数据调用无法隔离。登陆和未登陆手机无法区分

action

action 代表 group 下的不同接口,可以把 group 叫做接口组,action 代表接口。Sekiro 的服务调用路由最终到达 action 层面,实践经验一般来说同一个 app,或者说同一个业务,大多需要同时 暴露多个接口。action 最终会映射到一个固定的 java handler class 下。

SekiroClient.start("sekiro.virjar.com",clientId,"sekiro-demo")
        .registerHandler("clientTime",new SekiroRequestHandler(){
            @Override
            public void handleRequest(SekiroRequest sekiroRequest,SekiroResponse sekiroResponse){
                    sekiroResponse.success(" now:"+System.currentTimeMillis()+ " your param1:" + sekiroRequest.getString("param1"));
            }
        });

demo 代码中,会在 group:sekiro-demo下暴露一个 action 为:clientTime的服务。

clientId

clientId 用于区分不同手机,同一个接口可以部署在多个手机上,Sekiro 会通过轮询策略分配调用流量(暂时使用轮询策略),客户端需要自行保证 clientId 唯一。如果你没有特殊需要,可以通过 AndroidId,也可以使用随机数。 我更加建议使用手机本身的硬件 Id 作为 clientId,如手机序列号,IMEI。

bindClient

这是转发服务提供的一个特殊参数,用于指定特定的手机进行转发调用。如果你的业务在某个手机存在多次调用的事务一致性保证需求,或者你需要在 Sekiro 上层系统实现手机资源调用调度。那么通过传递参数可以是的 Sekiro 服务放弃调度策略 管理。或者在问题排查阶段,将请求发送到特定手机用于观察手机状态

备注: Hermes 系统中,存在资源可用性预测算法,使用动态因子完成调用流量负载均衡。Sekiro 有同步该算法的计划,除非有特殊需要,一般不建议 Sekiro 上游系统托管手机资源调度。

备注: 目前 Sekiro 存在四个保留参数,业务放不能使用这几个作为参数的 key,包括:group,action,clientId,invoke_timeOut

服务器接口

分组列举 /groupList

展示当前系统中注册过的所有 group,如果你新开发一个需求,请注意查询一下系统中已有 group,避免接口组冲突。内部系统定制可以实现 group 申请注册,分配注入准入密码等功能。

client 资源列举 /natChannelStatus

展示特定 group 下,注册过那些手机。上游如需托管调度,那么需要通过这个接口同步 client 资源。同时也依靠这个接口判定你的客户端是否正确注册。

调用转发 /invoke | /asyncInvoke

实现请求到手机的调用转发,区分 invoke 和 asyncInvoke,他们的接口定义一样,只是 asyncInvoke 使用异步 http server 实现,一般情况建议使用 asyncInvoke。

invoke 接口定义比较宽泛,可支持 GET/POST,可支持 application/x-www-form-urlencodedapplication/json,方便传入各种异构参数,不过大多数情况,Get 请求就足够使用。

client 接口

之前多次展示 client 的 demo,这里讲诉一下 client 的食用姿势。

通过 SekiroClient#start 获取一个 SekiroClient 实例,至少需要指定:服务器,clientId。Sekiro 存在多个重载的 start 方法,选择合适的使用即可。如:SekiroClient.start("sekiro.virjar.com",clientId,"sekiro-demo") 之后,你需要通过 registerHandler 注册接口,每个 handler 就类似 SpringMVC 里面的一个 controller 方法,用于处理一个特殊的调用请求。

备注: 可以看到,handleRequest 方法没有返回值,这是因为这个接口是异步的,当数据准备好之后通过 response 写会数据即可。千万注意,不要在这个接口中执行网络请求之类的耗时请求,如果有耗时调用,请单独设定线程或者线程池执行。

SekiroRequest 和 SekiroResponse

分别为业务方请求对象包装,和数据返回句柄。SekiroRequest 有一堆 Get 方法,可以获取请求内容。SekiroResponse 有一堆 send 方法用于回写数据。

自动参数绑定

类似 springMVC,在请求调用进入方法的时候,一般框架会支持请求内容自动转化为 java 对象。Sekiro 也支持简单的参数绑定。这可以使得 handler 代码更加优雅。 参数绑定包括参数自动注入,参数自动转换,参数校验等。这个功能可以节省部分 handler 入口对于参数的处理逻辑代码。 如 demo:

public class ClientTimeHandler implements SekiroRequestHandler {

    @Override
    public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
        sekiroResponse.success("process: " + DemoApplication.getInstance().getPackageName() + " : now:" + System.currentTimeMillis() + " your param1:" + sekiroRequest.getString("param1"));
    }
}

和如下代码等价:

public class ClientTimeHandler implements SekiroRequestHandler {
     //这里自动将请求参数中的param1绑定到handler对象的param1参数中
    @AutoBind
    private String param1;

    @Override
    public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
        sekiroResponse.success("process: " + DemoApplication.getInstance().getPackageName() + " : now:" + System.currentTimeMillis() + " your param1:" + param1);
    }
}

如果参数是一个 map,甚至是一个 java pojo 对象,这里可以支持自动注册。不过需要注意,匿名内部类的 handler 不支持自动绑定参数

日志规范

sekiro 代码 Android 和 java server 共用,但是日志框架两端对齐存在问题。服务器端我才用 logback+slf4j 的方案。但是这个方案在 Android 端无法较好的使用,由于 sekiro 多在代码注入环境下使用, Android 端的 slf4j 的驱动api 'com.github.tony19:logback-android:1.3.0-2'依赖 assets 资源配置,或者代码主动配置,这样灵活性不好。

针对于客户端和两端共用代码,我单独抽取日志模块。并实现他们在服务端环境和 Android 端环境的路由切换。Android 端使用原生 logger:android.util.Log,服务端使用 slf4j。

sekiro 整体日志,使用同一个 logger 输出。不提供不同模块日志开关或者输出等各种自定义需求。android 端使用 tag:Sekiro,服务端使用 name 为:Sekiro的 logger。 不过这个名字可以被修改,他是一个静态变量:com.virjar.sekiro.log.SekiroLogger.tag

在 android logcat 中,可以通过 tag 过滤 sekiro 相关日志:

virjar-share:com.southwestairlines.mobile virjar$ adb logcat -s Sekiro
--------- beginning of system
--------- beginning of crash
--------- beginning of main
11-17 16:28:36.439 27941 27995 I Sekiro  : test sekiro log
11-17 16:28:36.439 27941 27995 I Sekiro  : connect to nat server at service startUp
11-17 16:28:36.450 27941 27997 I Sekiro  : connect to nat server...
11-17 16:28:36.505 27941 28000 I Sekiro  : connect to nat server success:[id: 0x9f83ed84, L:/192.168.0.10:41434 - R:sekiro.virjar.com/47.94.106.20:5600]
11-17 16:28:41.624 27941 28000 I Sekiro  : receive invoke request: group=sekiro-demo&action=clientTime&param1=%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%82%E6%95%B0  requestId: 15
11-17 16:28:41.656 27941 28000 I Sekiro  : invoke response: {"data":"process: com.virjar.sekiro.demoapp : now:1573979321626 your param1:自定义参数","ok":true,"status":0}
11-17 16:28:44.443 27941 28000 I Sekiro  : receive invoke request: group=sekiro-demo&action=clientTime&param1=%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%82%E6%95%B0  requestId: 16
11-17 16:28:44.445 27941 28000 I Sekiro  : invoke response: {"data":"process: com.virjar.sekiro.demoapp : now:1573979324444 your param1:自定义参数","ok":true,"status":0}
11-17 16:28:45.620 27941 28000 I Sekiro  : receive invoke request: group=sekiro-demo&action=clientTime&param1=%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%82%E6%95%B0  requestId: 17
11-17 16:28:45.624 27941 28000 I Sekiro  : invoke response: {"data":"process: com.virjar.sekiro.demoapp : now:1573979325621 your param1:自定义参数","ok":true,"status":0}

如果你想托管日志输出规则,那么通过静态方法:com.virjar.sekiro.log.SekiroLogger.setLogger(com.virjar.sekiro.log.ILogger logger)覆盖默认实现即可

其他语言

目前 sekiro 定位为 Android 框架,如果你在其他平台,或者使用 java 以外的语言接入 sekiro,那么你需要自己实现 Sekiro 的交互协议

协议参见 Sekiro 二进制协议文档: protoal.md

web 注入

Sekiro 已支持 websocket 协议,使用本功能可以支持注入 js 到浏览器后,调用浏览器环境的 js 代码。 Web 环境基于 WebSocket 实现,使用方法也很简单:

 <script type="text/javascript" src="http://file.virjar.com/sekiro_web_client.js?_=123"></script>
    <script type="text/javascript">

        var client = new SekiroClient("wss://sekiro.virjar.com/websocket?group=ws-group&clientId=testClient");
        client.registerAction("clientTime",function(request, resolve,reject ){
            resolve(""+new Date());
        })

    </script>

你可以运行我们提供的 demo 测试 Sekiro 的 JS RPC 能力 js_rpc_sekiro_demo.html

sekiro_demo.gif

sekiro_demo.gif

ssl 问题

如果你要注入的网页是 https 的,那么直接通过我们的 websocket 服务会被浏览器拦截。那么你需要使得你的服务器支持 ssl WebSocket,Sekiro 的 demo 网站已经完成了相关配置。 此时你应该使用 wss:协议替代:ws:,如:wss://sekiro.virjar.com/websocket?group=ws-group&clientId=testClient

相关分析文章

https://github.com/langgithub/sekiro-lang

https://bbs.nightteam.cn/thread-86.htm

https://www.jianshu.com/p/6b71106c45eb?from=timeline

qq Group

569543649

合作

开源即免费,我不限制你们拿去搞事情,但是开源并不代表义务解答问题。如果你发现了有意思的 bug,或者有建设性意见,我乐意参与讨论。 如果你想寻求解决方案,但是又没有能力驾驭这个项目,欢迎走商务合作通道。联系 qq:819154316,或者加群:569543649。 拒绝回答常见问题!!!

内部培训

Sekiro 高阶培训和部分抓取技术课程可在此连接购买 猿人学·爬虫进阶课

捐赠

如果你觉得作者辛苦了,可以的话请我喝杯咖啡 alipay

reward2


文章来源: https://github.com/virjar/sekiro
如有侵权请联系:admin#unsafe.sh