用了很久 panda 这个软件了,感觉还是非常不错的,速度、延迟都还是非常好的。但三天的免费使用时长确实有点短,于是有了付费使用的想法。但是一看价格,每月 9.9 美元直接把我劝退。但作为重度白嫖用户,我网上搜了一下发现并没有破解版的 panda,于是,我决定用自己贼菜的技术尝试破解一下。

# 尝试 1:采用脚本批量注册试用账号

首先,先尝试进行抓包。直接抓包并没有显示证书问题,说明并该软件并没有使用 sslpinning 反抓包技术,这就简单多了,直接软件数据,使用应用变量重新生成一个设备码,然后开启抓包,打开 app,得到抓包结果。

分析抓包得到的数据,发现注册逻辑如下:

  1. 首先向接口 post 请求设备信息,查询该设备是否注册过账号。
  2. 如果注册过账号,直接获得该设备绑定的 userNumber 和 authorization,并标记为登录状态。如果未注册过,则进入注册流程。
  3. 注册流程:向接口请求获取 userNumber,获取到后再次向接口进行一次携带 userNamber 值的 account-auto-generation 请求,且本次请求会自动获取并记录 authorization。

分析后发现,默认账户密码为系统生成的设备 ID(非 IMEI 码)。于是尝试用 PHP 写了一个注册脚本,如下:

p
<?php
// 请求模拟设置区域
$id = randomkeys(33);
// 获取 userNumber
$time = time();
$headers = \[
    "device-identifier: ".$id,
    "api-version: v1.0",
    "accept-language: zh-CN",
    "accept: application/json",
    "user-Agent: okhttp/3.8.0 android/10(OnePlus) panda/5.3.0(71)",
    "device-type: ANDROID",
    "product-identifier: panda",
    "x-timestamp:".$time,
    "content-type: application/json; charset=UTF-8"
          \];
$userNumberData = '{"clientVersion":"5.3.0","deviceToken":"'.$id.'","deviceType":"ANDROID"}';
$userNumberResponse = post('https://api.panhvhg.xyz/api/register/user-number', $userNumberData, $headers);
$userBumberJson = json\_decode($userNumberResponse,true);
$userNumber = $userBumberJson\['data'\];
// 获取 authorization
$time = time();
$headers = \[
    "device-identifier: ".$id,
    "api-version: v1.0",
    "accept-language: zh-CN",
    "accept: application/json",
    "user-Agent: okhttp/3.8.0 android/10(OnePlus) panda/5.3.0(71)",
    "device-type: ANDROID",
    "product-identifier: panda",
    "x-timestamp:".$time,
    "content-type: application/json; charset=UTF-8"
          \];
$authData = '{"clientVersion":"5.3.0","deviceName":"Android 10 SDK 29 OnePlus ONE A2001","deviceToken":"'.$id.'","deviceType":"ANDROID","number":'.$userNumber.',"password":""}';
$authResponse = post('https://api.panhvhg.xyz/api/register/trier-account-auto-generation', $authData, $headers);
$authJson = json\_decode($authResponse,true);
$auth = $authJson\['data'\]\['accessToken'\];
//var\_dump($authResponse);
 /\*
// 访问 order
$time = time();
$headers = \[
    "device-identifier: ".$id,
    "api-version: v1.0",
    "accept-language: zh-CN",
    "accept: application/json",
    "user-Agent: okhttp/3.8.0 android/10(OnePlus) panda/5.3.0(71)",
    "device-type: ANDROID",
    "product-identifier: panda",
    "x-timestamp:".$time,
    "content-type: application/json; charset=UTF-8",
    "authorization:Bearer ".$auth
          \];
//$order = get('https://api.panhvhg.xyz/api/orders/active',$headers);
// 设置密码
        
$time = time();
$headers = \[
    "device-identifier: ".$id,
    "api-version: v1.0",
    "accept-language: zh-CN",
    "accept: application/json",
    "user-Agent: okhttp/3.8.0 android/10(OnePlus) panda/5.3.0(71)",
    "device-type: ANDROID",
    "product-identifier: panda",
    "x-timestamp:".$time,
    "content-type: application/json; charset=UTF-8",
    "authorization:Bearer ".$auth
          \];
$password=randomkeys(8);
$passwordData = '{"newPassword":"'.$password.'","oldPassword":"'.$id.'"}';
$passwordResponse = postHeard('https://api.panhvhg.xyz/api/users/change-password', $passwordData, $headers);
$result = \['code'=>1,'msg'=>'获取成功!','userNumber'=>$userNumber,'password'=>$password\];
$result = json\_encode($result);
\*/
$time = time();
$headers = \[
    "device-identifier: ".$id,
    "api-version: v3.0",
    "accept-language: zh-CN",
    "accept: application/json",
    "user-Agent: okhttp/3.8.0 android/10(OnePlus) panda/5.3.0(71)",
    "device-type: ANDROID",
    "product-identifier: panda",
    "x-timestamp:".$time,
    "content-type: application/json; charset=UTF-8",
    "authorization:Bearer ".$auth,
    "request\_raw\_response\_body\_tag\_header: 8"
          \];
$fina = post('https://api.panhvhg.xyz/api/v3/channels/738/connect','',$headers);
echo $fina;
// 设备 ID 生成
function randomkeys($length) 
{ 
$key = '';
$pattern = '1234567890ABCDEFGHIJKLOMNOPQRSTUVWXYZ';
for($i=0;$i<$length;$i++) 
{ 
$key .= $pattern{mt\_rand(0,35)}; // 生成 php 随机数 
} 
return $key; 
}         
//post-curl 函数封装
function post($url, $data, $headers) {
// 初使化 init 方法
$ch = curl\_init();
// 指定 URL
curl\_setopt($ch, CURLOPT\_URL, $url);
// 设定请求后返回结果
curl\_setopt($ch, CURLOPT\_RETURNTRANSFER, 1);
// 声明使用 POST 方式来进行发送
curl\_setopt($ch, CURLOPT\_POST, 1);
// 发送什么数据呢
curl\_setopt($ch, CURLOPT\_POSTFIELDS, $data);
// 忽略证书
curl\_setopt($ch, CURLOPT\_SSL\_VERIFYPEER, false);
curl\_setopt($ch, CURLOPT\_SSL\_VERIFYHOST, false);
curl\_setopt($ch, CURLOPT\_HTTPHEADER,$headers);
// 设置超时时间
curl\_setopt($ch, CURLOPT\_TIMEOUT, 10);
// 发送请求
$output = curl\_exec($ch);
// 关闭 curl
curl\_close($ch);
// 返回数据
return $output;
}
//post-curl 获取响应头
function postHeard($url, $data, $headers) {
// 初使化 init 方法
$ch = curl\_init();
// 指定 URL
curl\_setopt($ch, CURLOPT\_URL, $url);
// 设定请求后返回结果
curl\_setopt($ch, CURLOPT\_RETURNTRANSFER, 1);
// 返回 response\_header, 该选项非常重要,如果不为 true, 只会获得响应的正文
curl\_setopt($ch, CURLOPT\_HEADER, true);
// 是否不需要响应的正文,为了节省带宽及时间,在只需要响应头的情况下可以不要正文
curl\_setopt($ch, CURLOPT\_NOBODY, false);
// 声明使用 POST 方式来进行发送
curl\_setopt($ch, CURLOPT\_POST, 1);
// 发送什么数据呢
curl\_setopt($ch, CURLOPT\_POSTFIELDS, $data);
// 忽略证书
curl\_setopt($ch, CURLOPT\_SSL\_VERIFYPEER, false);
curl\_setopt($ch, CURLOPT\_SSL\_VERIFYHOST, false);
curl\_setopt($ch, CURLOPT\_HTTPHEADER,$headers);
// 设置超时时间
curl\_setopt($ch, CURLOPT\_TIMEOUT, 10);
// 发送请求
$output = curl\_exec($ch);
// 获得响应结果里的:头大小
$headerSize = curl\_getinfo($ch, CURLINFO\_HEADER\_SIZE);
// 关闭 curl
curl\_close($ch);
// 返回数据
return $output;
}
function get($url, $headers) {
   // 初使化 init 方法
   $ch = curl\_init();
   // 指定 URL
   curl\_setopt($ch, CURLOPT\_URL, $url);
   // 设定请求后返回结果
   curl\_setopt($ch, CURLOPT\_RETURNTRANSFER, 1);
   // 声明使用 POST 方式来进行发送
   curl\_setopt($ch, CURLOPT\_POST, false);
   // 忽略证书
   curl\_setopt($ch, CURLOPT\_SSL\_VERIFYPEER, false);
   curl\_setopt($ch, CURLOPT\_SSL\_VERIFYHOST, false);
   curl\_setopt($ch, CURLOPT\_HTTPHEADER,$headers);
   // 设置超时时间
   curl\_setopt($ch, CURLOPT\_TIMEOUT, 10);
   // 发送请求
   $output = curl\_exec($ch);
   // 关闭 curl
   curl\_close($ch);
   // 返回数据
   return $output;
}
?>

本以为到此就大功告成,但发现注册的账号登录时提示 “试用账号不允许其他设备登录”,于是宣告这个思路失败。

# 尝试 2:直接获取节点信息

此类 npv 软件无非就是使用常用的类 sockets5 代理协议来实现的富强。于是决定换一种思路,直接提取线路信息。

首先,同样先用抓包来进行尝试。打开抓包软件,打开 APP,点击连接按钮,返回后查看抓包信息,如下:

首先,根据返回的 json 数据中的 “meta” 中 “protocol” 的值发现使用的是 “shadowsocks” 协议。在其他键值对中并没有发现 ss 链接或节点信息,于是推测,节点信息保存在被加密的 “data” 值中。根据被加密字符串的特点来看,推测是使用了 aes 加密,但由于密钥我们无从得知,所以只能通过反编译来查看加密方式和密钥。

打开 MT 管理器,点击该安装包发现该 APP 并未加固。那我们直接点击查看即可。使用 dex 编辑器打开 dex 文件,我们直接尝试搜索 “parser”,搜索类型选择 “类”,惊喜的发现,得到了一个结果。

打开该类,直接便发现了显眼的 “AES/ECB/PKCS5Padding” 字样,以及 “key” 字样与相邻的 “panda&beta#12345” 字样,这这这.... 就这么简单就找到加密方式与密钥了?于是打开一个 aes 的解密工具,尝试破解一下 data 数据。

直接得到了节点信息。于是尝试进行 ss 的连接。打开 v2rayNG,选择手动输入 [shadowsocks]。连接,然后发现无网。仔细观察解密后的 data 数据,发现有一个键为 “timeout”,并且连续多次请求返回的端口值都不相同,不同账户的 password 值都相同。于是可以推测该软件的连接思路如下:连接时通过 post 请求节点的 connect 信息,同时 ss 服务端开放相应端口 “timeout” 值的时间,如果没有在规定时间内发起连接,端口就会拒绝本次连接。由于端口值一直在变且有连接时间限制,所以我们无法直接获取有效的节点信息。

本次尝试再次以失败告终。

# 最后尝试:修改 API + 反代 connect 接口

推测出该软件的连接原理后,又有了一种新的破解思路。我们可以直接修改该软件的 API 接口,然后在服务器上搭建一个返回虚假数据的 API 来达到破解的目的,节点信息的话可以直接反代真实 API 的 connect 接口来实现。

直接使用 thinkphp 框架快速搭建一个接口,然后完善路由和返回数据,由于没啥技术含量,在此就不再赘述。

修改 API 我们还是使用 mt 管理器,编辑 dex 文件,搜索关键字 “API”,然后排除 sdk 中的 API 类信息,找到了 API 所在位置:com.pandavpn.androidproxy.ApiConst

根据抓包抓到的接口,我们修改接口为搭建的虚假接口,然后保存,签名,安装即可。

然后打开,点击连接,成功!

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

HuaYu 微信支付

微信支付

HuaYu 支付宝

支付宝

HuaYu 贝宝

贝宝