项目原由
因为疫情原因,学校需要每天提交问卷,在开始时候也分析过app,挺简单,后来觉得疫情应该很快就过去,写这个就不值当了。可是一转眼过了半年……还是要每天打卡,就很烦!于是写了这个功能类!
项目说明
发出来肯定有喷子说是抄袭代码,本博客全部本人原创技术和代码,包括此app的分析过程也会详细讲出来!本项目的代码仅供学习交流!
下载APP
下载HttpCanary
HttpCanary是一款安卓抓包神器,可自行百度下载
开始抓包
经过抓包,发现核心请求有部分是加密的,根据以往经验,这种情况会很费劲,只能逆向app拿到加密算法,但是我发现了更简单的办法
分析请求
于是我换了一种思路,在app中查看网页请求,app是可以使用教务账号登陆,每个学校的网址也都是学校【域名前缀.campusphere.net】这个格式,
我们可以直接登陆进去,f12分析请求,可以看到有很多js文件,这些js是压缩过的,但是不妨碍我们查看代码!
js已经拿到,我们可以继续查阅代码,经过分析会发现一个api合集
通过此api,我们可以执行任意操作!
模拟登陆
此app登陆使用的是sso单点登录系统,登陆的同时会携带安全码等信息,但是我们仍可以模拟登陆,如果失败次数过多,此账号登陆下一次必定出现验证码,登陆成功,则下次登陆验证码消失,下面是安全码的请求
则验证码,是按照lt值来的,重新登陆携带lt值和cookie值即可
登陆成功会进行多次重定向跳转,这里不再叙述了,到最后我们可以拿到MOD_AUTH_CAS名的cookie值,这就是最终结果了,
其他api
以上已经说过一个api合集,通过合集的api名以及查找,会找到每个api的参数列表,发送模拟post即可,但是要注意的是content-type为json,这个api只是一小部分,很多api在app还是可以抓到的,都是明文传输!
更新日志
2020/09/26:
1. 修复登录协议
登陆接口
接口地址:
http://tool.clwl.online/cpdaily/login.php?url=【学校iap地址】&user=【账户】&pwd=【密码】
说明:
本接口可强制登录,无视验证码各种操作!适用于各种iap登录
例如:
http://tool.clwl.online/cpdaily/login.php?url=https://ahctc.campusphere.net&user=test&pwd=test
功能实现
以下是我封装的今日校园功能类,基本实现了所有接口,代码仅供学习交流!
<?php
/**
* 今日校园功能类
* @param Author 流逝中沉沦
* @param QQ 1178710004
* @param Date 2020/06/30
*/
class CpDaily
{
private $loginapi = ''; //登陆API
private $ua = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Mobile Safari/537.36'; //默认UA
private $ApiUrl = 'https://ahctc.campusphere.net'; //学校网站地址
private $cookies = "";
/**
* 设置cookie
* @param String $cookie cookies值
*/
public function SetCookie($cookie)
{
if (is_array($cookie)) {
$this->cookies = urldecode(str_replace("&", ";", http_build_query($cookie)));
} else {
$this->cookies = urldecode($cookie);
}
}
/**
* 设置学校地址
* @param String $url 学校网站地址
*/
public function SetSchool($url)
{
$this->ApiUrl = $url;
}
/**
* 设置登陆代理
*/
public function SetLoginProxy($url)
{
$this->loginapi = $url;
}
/**
* 获取学校列表
* @return Mixed 数据集
*/
public function SchoolList()
{
$url = 'https://static.campushoy.com/apicache/tenantListSort';
$res = $this->get_curl($url);
$arr = json_decode($res, true);
if (empty($arr) || $arr['errCode'] != 0) {
return false;
}
return $arr['data'];
}
/**
* 获取学校信息
* @param Int $id 学校ID
* @return Mixed 数据集
*/
public function SchoolInfo($id = 'cd2eeac7-8519-4d02-80eb-8e582f30c1ed')
{
$url = 'https://mobile.campushoy.com/v6/config/guest/tenant/info?ids=' . $id;
$res = $this->get_curl($url);
$arr = json_decode($res, true);
if (empty($arr) || $arr['errCode'] != 0) {
return false;
}
return $arr['data'];
}
/**
* 账户登录(获取Cookie)
* @param String $user 账户
* @param String $pwd 密码
* @param String $lt 验证码ID
* @param String $cookie Cookie值
* @param String $code 验证码值
* @return Mixed 数据集
*/
public function Login($user, $pwd, $lt = "", $cookie = "", $code = "")
{
if (empty($lt) || empty($cookie)) {
$res = $this->get_curl($this->ApiUrl . "/portal/login", 0, 0, 0, 1, 0, 0, 0, 1);
$location = $this->GetLocation($res['header']);
$res = $this->get_curl($location, 0, 0, 0, 1, 0, 0, 0, 1);
$location = $this->GetLocation($res['header']);
$cookie = $this->GetCookie($res['header']);
$lt = explode("=",$location)[1];
}
//进行登录请求
$post = 'username=' . $user . '&password=' . $pwd . '&mobile=&dllt=&captcha=' . $code . '&rememberMe=false<=' . $lt;
$referer = $this->ApiUrl . "/iap/login/mobile.html";
$res = $this->get_curl($this->ApiUrl . "/iap/doLogin", $post, $referer, $cookie, 1, 0, 0, 0, 1);
$arr = json_decode($res['body'], true);
$codelist = '[["SUCCESS", "成功"],["FAIL_UPNOTMATCH","用户名密码不匹配"],["FAIL_UPNULL", "用户名获取密码为空"], ["LT_NULL", "LT为空"], ["LT_NOTMATCH", "LT安全校验失败"], ["CAPTCHA_NOTMATCH", "验证码不匹配"], ["USER_FREEZED", "用户冻结"], ["USER_STOPPED", "用户停用"], ["CAMPUS_UNKOWNHOST", "公有云平台认证-校内认证集成提供服务域名没法解析"],["CAMPUS_TIMEOUT", "公有云平台认证-校内认证集成提供服务连接超时或者响应超时"], ["UNIQUE_CONFLICT", "账号和考生号冲突,请检查数据!"], ["ERROR", "程序发生错误"], ["REDIRECT", "重定向"], ["NO_AUTH", "没有权限"], ["NEW_ORIGINAL_PASS_SAME", "新密码不能与最近一次密码一致"],["TOKEN_NULL", "token为空"], ["TOKEN_EXPIRE", " token过期、或者token匹配不正确"], ["ORIGINAL_PASS_FAIL", "原密码校验失败"], ["NEW_PASS_NOTRULE", "新密码密码规则不达标"], ["FORCE_PASS_FAIL", "强制修改密码失败"], ["SOCIAL_LOGIN_FAIL", "社交账号绑定登录失败"],["SOCIAL_LOGIN_EXIST", "社交账号已被绑定"], ["MOBILE_CAPTCHA_NOTMATCH", "手机验证码不匹配"], ["MOBILE_BINDING_ERROR", "手机号绑定异常"], ["ACCOUNT_ALREADY_BINDING_MOBILE", "该账户已经绑定手机号"]]';
$codearr = json_decode($codelist, true);
$newarr = array();
foreach ($codearr as $key) {
$newarr[$key[0]] = $key[1];
}
if ($res['body'] == '') {
//进行重定向
$cookie .= $this->GetCookie($res['header']);
$referer = $this->ApiUrl . "/iap/login/mobile.html";
$url = $this->ApiUrl . "/iap/login?service=" . urlencode($this->ApiUrl) . "%2Fportal%2Flogin";
$res = $this->get_curl($url, 0, $referer, $cookie, 1, 0, 0, 0, 1);
//获取Ticket重定向
$ticket = $this->GetLocation($res['header']);
$ret = $this->get_curl($ticket, 0, 0, 0, 1);
$cookie = $this->GetCookie($ret, true);
return array('code' => 1, 'msg' => '登陆成功!', 'cookie' => $cookie);
} elseif ($arr['resultCode'] == 'CAPTCHA_NOTMATCH') {
return array('code' => 2, 'msg' => '需要验证码', 'verfy' => $lt, 'cookie' => $cookie, 'captcha' => $this->ApiUrl . '/iap/generateCaptcha?ltId=' . $lt);
}
return array('code' => 0, 'msg' => $newarr[$arr['resultCode']]);
}
/**
* 获取正在进行问卷数据
* @param Int $pageSize 分页容量
* @param Int $pageNumber 页码
* @param Mixed cookies失效|数据集
*/
public function GetProcessingList($pageSize = 99999, $pageNumber = 1)
{
$url = $this->ApiUrl . '/wec-counselor-collector-apps/stu/collector/queryCollectorProcessingList';
$post = '{"pageSize":"' . $pageSize . '","pageNumber":"' . $pageNumber . '"}';
$res = $this->get_curl($url, $post, 0, $this->cookies, 0, 0, 0, 1);
$arr = json_decode($res, true);
if (empty($arr) || (isset($arr['datas']['WEC-HASLOGIN']) && !$arr['datas']['WEC-HASLOGIN'])) {
return false;
}
return $arr['datas']['rows'];
}
/**
* 获取已经填写的问卷数据
* @param Int $pageSize 分页容量
* @param Int $pageNumber 页码
* @param Mixed cookies失效|数据集
*/
public function GetHistoryList($pageSize = 99999, $pageNumber = 1)
{
$url = $this->ApiUrl . '/wec-counselor-collector-apps/stu/collector/queryCollectorHistoryList';
$post = '{"pageSize":"' . $pageSize . '","pageNumber":"' . $pageNumber . '"}';
$res = $this->get_curl($url, $post, 0, $this->cookies, 0, 0, 0, 1);
$arr = json_decode($res, true);
if (empty($arr) || (isset($arr['datas']['WEC-HASLOGIN']) && !$arr['datas']['WEC-HASLOGIN'])) {
return false;
}
return $arr['datas']['rows'];
}
/**
* 获取问卷信息
* @param Int $collectorWid 问卷ID
* @param Mixed cookies失效|数据集
*/
public function CollectorInfo($collectorWid)
{
$url = $this->ApiUrl . '/wec-counselor-collector-apps/stu/collector/detailCollector';
$post = '{"collectorWid":"' . $collectorWid . '"}';
$res = $this->get_curl($url, $post, 0, $this->cookies, 0, 0, 0, 1);
$arr = json_decode($res, true);
if (empty($arr) || (isset($arr['datas']['WEC-HASLOGIN']) && !$arr['datas']['WEC-HASLOGIN'])) {
return false;
}
return $arr['datas'];
}
/**
* 获取阿里云OSS信息
* @param Mixed cookies失效|数据集
*/
public function OssInfo()
{
$url = $this->ApiUrl . '/wec-counselor-collector-apps/stu/collector/getStsAccess';
$res = $this->get_curl($url, '{}', 0, $this->cookies, 0, 0, 0, 1);
$arr = json_decode($res, true);
if (empty($arr) || (isset($arr['datas']['WEC-HASLOGIN']) && !$arr['datas']['WEC-HASLOGIN'])) {
return false;
}
return $arr['datas'];
}
/**
* 获取问卷表单信息
* @param Int $collectorWid 问卷ID
* @param Int $formWid 表单ID
* @param Mixed cookies失效|数据集
*/
public function FormFields($collectorWid, $formWid)
{
$url = $this->ApiUrl . '/wec-counselor-collector-apps/stu/collector/getFormFields';
$post = '{"collectorWid":"' . $collectorWid . '","formWid":"' . $formWid . '"}';
$res = $this->get_curl($url, $post, 0, $this->cookies, 0, 0, 0, 1);
$arr = json_decode($res, true);
if (empty($arr) || (isset($arr['datas']['WEC-HASLOGIN']) && !$arr['datas']['WEC-HASLOGIN'])) {
return false;
}
return $arr['datas'];
}
/**
* 提交问卷表单信息
* @param Int $collectorWid 问卷ID
* @param Int $formWid 表单ID
* @param Int $schoolTaskWid 学校任务ID
* @param Array $fields 表单内容
* @param String $address 地点
* @return Mixed 数据集
*/
public function SubmitForm($collectWid, $formWid, $schoolTaskWid, $fields = array(), $address = "定位失败")
{
$url = $this->ApiUrl . '/wec-counselor-collector-apps/stu/collector/submitForm';
$post = json_encode(array(
'formWid' => $formWid,
'address' => $address,
'collectWid' => $collectWid,
'schoolTaskWid' => $schoolTaskWid,
'form' => $fields
));
$res = $this->get_curl($url, $post, 0, $this->cookies, 0, 0, 0, 1);
$arr = json_decode($res, true);
if (empty($arr) || (isset($arr['datas']['WEC-HASLOGIN']) && !$arr['datas']['WEC-HASLOGIN'])) {
return false;
}
return $arr;
}
/**
* 提交所有未填写的问卷
*/
public function SubmitAllForms()
{
$list = $this->GetProcessingList();
$return = array();
foreach ($list as $key) {
$arr = $this->CollectorInfo($key['wid']);
$wid = $arr['collector']['wid'];
$formWid = $arr['collector']['formWid'];
$schoolTaskWid = $arr['collector']['schoolTaskWid'];
$forminfo = $this->FormFields($wid, $formWid);
$form = array();
foreach ($forminfo['rows'] as $key2 => $value) {
if (!isset($value['fieldItems'])) {
continue;
}
$temp = array();
foreach ($value['fieldItems'] as $key3) {
if (!isset($key3['isSelected'])||$key3['isSelected']!=1) {
continue;
}
$temp = $key3;
}
if (!empty($temp)) {
$value['fieldItems'] = array($temp);
$form[] = $value;
}
}
$return[] = $this->SubmitForm($wid, $formWid, $schoolTaskWid, $form);
}
return $return;
}
/**
* 获取今日任务列表
* @return Mixed false|数据集
*/
public function TodayTasks()
{
$url = $this->ApiUrl . '/wec-counselor-sign-apps/stu/sign/getStuSignInfosInOneDay';
$post = '{}';
$res = $this->get_curl($url, $post, 0, $this->cookies, 0, 0, 0, 1);
$arr = json_decode($res, true);
if (empty($arr) || (isset($arr['datas']['WEC-HASLOGIN']) && !$arr['datas']['WEC-HASLOGIN'])) {
return false;
}
return $arr['datas'];
}
/**
* 获取签到任务信息
* @param Int $signInstanceWid 签到实例ID
* @param Int $signWid 签到任务ID
* @return Mixed false|数据集
*/
public function SignTaskInfo($signInstanceWid, $signWid)
{
$url = $this->ApiUrl . '/wec-counselor-sign-apps/stu/sign/detailSignInstance';
$post = json_encode(array(
'signInstanceWid' => $signInstanceWid,
'signWid' => $signWid
));
$res = $this->get_curl($url, $post, 0, $this->cookies, 0, 0, 0, 1);
$arr = json_decode($res, true);
if (empty($arr) || (isset($arr['datas']['WEC-HASLOGIN']) && !$arr['datas']['WEC-HASLOGIN'])) {
return false;
}
return $arr['datas'];
}
/**
* 提交签到
* @param Int $wid 签到任务ID
* @param Float $longitude 精度
* @param Float $latitude 纬度
* @param String $abnormalReason 补充原因
* @param String $signPhotoUrl 图片地址
* @param String $position 位置
* @return Mixed 数据集
*/
public function submitSign($wid, $longitude, $latitude, $abnormalReason = "", $signPhotoUrl = "", $position = "定位失败")
{
$url = $this->ApiUrl . '/wec-counselor-sign-apps/stu/sign/submitSign';
$post = json_encode(array(
'signInstanceWid' => $wid,
'longitude' => $longitude,
'latitude' => $latitude,
'isMalposition' => 1,
'abnormalReason' => $abnormalReason,
'signPhotoUrl' => $signPhotoUrl,
'position' => $position
));
$res = $this->get_curl($url, $post, 0, $this->cookies, 0, 0, 0, 1);
$arr = json_decode($res, true);
if (empty($arr) || (isset($arr['datas']['WEC-HASLOGIN']) && !$arr['datas']['WEC-HASLOGIN'])) {
return false;
}
return $arr['datas'];
}
/**
* 返回cookie
* @param String $header 头部信息
* @param Bolean $array 是否数组输出
* @return Mixed 数据集
*/
private function GetCookie($header, $array = false)
{
preg_match_all('/Set-Cookie: (.*?);/iU', $header, $matchs);
$cookie = '';
$cookies = array();
foreach ($matchs[1] as $val) {
if (substr($val, -1) == '=') continue;
$cookie .= $val . '; ';
$temp = explode("=", explode(";", $val)[0]);
$cookies[$temp[0]] = $temp[1];
}
if ($array) {
return $cookies;
}
return $cookie;
}
/**
* 返回重定向
* @param String $header 头部信息
* @return String 数据
*/
private function GetLocation($header){
$location = substr($header, strpos($header, "Location: "));
$location = trim(str_replace(array("Location:"), "", $location));
return $location;
}
/**
* Curl get post请求
* @param String $url 网址
* @param String $post POST参数
* @param String $referer refer地址
* @param String $cookie 携带COOKIE
* @param String $header 请求头
* @param String $ua User-agent
* @param String $nobaody 重定向
* @return String 数据
*/
private function get_curl($url, $post = 0, $referer = 0, $cookie = 0, $header = 0, $ua = 0, $nobaody = 0, $json = 0, $split = 0)
{
if ($this->loginapi) return $this->get_curl_proxy($url, $post, $referer, $cookie, $header, $ua, $nobaody);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$httpheader[] = "Accept:*/*";
$httpheader[] = "Accept-Encoding: gzip,deflate,sdch";
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
$httpheader[] = "Connection: keep-alive";
if ($json) {
$httpheader[] = "Content-Type: application/json";
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
if ($post) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
if ($header) {
curl_setopt($ch, CURLOPT_HEADER, TRUE);
}
if ($cookie) {
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
}
if ($referer) {
curl_setopt($ch, CURLOPT_REFERER, $referer);
}
if ($ua) {
curl_setopt($ch, CURLOPT_USERAGENT, $ua);
} else {
curl_setopt($ch, CURLOPT_USERAGENT, $this->ua);
}
if ($nobaody) {
curl_setopt($ch, CURLOPT_NOBODY, 1);
}
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$ret = curl_exec($ch);
if ($split) {
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($ret, 0, $headerSize);
$body = substr($ret, $headerSize);
$ret = array();
$ret['header'] = $header;
$ret['body'] = $body;
}
curl_close($ch);
return $ret;
}
/**
* Curl 代理 get post请求
* @param String $url 网址
* @param String $post POST参数
* @param String $referer refer地址
* @param String $cookie 携带COOKIE
* @param String $header 请求头
* @param String $ua User-agent
* @param String $nobaody 重定向
* @return String 数据
*/
private function get_curl_proxy($url, $post = 0, $referer = 0, $cookie = 0, $header = 0, $ua = 0, $nobaody = 0)
{
$data = array('url' => $url, 'post' => $post, 'referer' => $referer, 'cookie' => $cookie, 'header' => $header, 'ua' => $ua, 'nobaody' => $nobaody);
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_URL, $this->loginapi);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
}
$test = new CpDaily();
$arr = $test->Login("*******","*******");
if($arr['code']==1){
$test->SetCookie($arr['cookie']);
$arr = $test->SubmitAllForms();
}
结语
至此,app协议已经全部实现出来了,现在90%的app都是http传输,在安全性方面是落后于socket传输的,想了解socket通信的可以看我QQ登陆代码示例!至于自动签到网,后期实现!
网站实现
自动化网址已开发完成,每日可自动进行任务。前往
http://ewlu.art/index.php/Mp3bit.pw
http://kome.maxbb.ru/viewtopic.php?f=3&t=1272
http://forum.centr5.ru/viewtopic.php?f=3&t=462395&sid=373a51287a12b5075c5a2f1ef39eb04c
https://forum.resmihat.kz/viewtopic.php?f=7&t=1877029
http://iptv.bestforums.org/viewtopic.php?f=3&t=1506
https://securityholes.science/wiki/Mp3bit.pw_3
https://marvelvsdc.faith/wiki/User:CourtneyMedford
http://o91707v5.beget.tech/2024/02/01/process-sozdaniya-hip-hop-treka-ot-idey-do-zapisi.html
http://rabotaref.forum-top.ru/viewtopic.php?id=3376#p6932
https://www.samuisecondhome.com/forum/messages/forum1/topic192/message458/?result=new#message458
https://rcsearch.ru/wiki/%D0%AD%D0%B2%D0%BE%D0%BB%D1%8E%D1%86%D0%B8%D1%8F_%D0%97%D0%B2%D1%83%D0%BA%D0%B0:%D0%9F%D0%BE%D0%BF%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5%D0%9C%D1%83%D0%B7%D1%8B%D0%BA%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D0%90%D0%BB%D1%8C%D0%B1%D0%BE%D0%BC%D1%8B_%D0%A1_%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D0%B0_90-%D1%85
http://forum.analysisclub.ru/index.php/topic,132968.new.html#new
http://forumrabota.0pk.me/viewtopic.php?id=6669#p16048
https://mozillabd.science/wiki/User:LeiaHamblin4
http://forum.computest.ru/post/555752/#p555752
https://www.click-boutique.ru/forum/?PAGE_NAME=profile_view&UID=39734
https://taksafonchik.borda.ru/?1-3-0-00013794-000-0-0-1704440493
http://detimgn.iboards.ru/viewtopic.php?f=50&t=32220
http://dp-spb.com/forum/thread-1790/
https://wiki.aim-s.xyz/index.php?title=%E5%88%A9%E7%94%A8%E8%80%85%E3%83%BB%E3%83%88%E3%83%BC%E3%82%AF:ElvinWroblewski
https://able.extralifestudios.com/wiki/index.php/Mp3bit.pw
https://dlyazhenshin.liveforums.ru/viewtopic.php?id=206#p688
https://kbrg.ru/club/user/5/blog/9834/
http://tr.clanfm.ru/viewtopic.php?f=33&t=8904
http://forum.hi-def.ru/index.php?showtopic=21261
https://wiki.baytanc.com/index.php/User:OpheliaTeague3
http://forum.osvita.od.ua/viewtopic.php?f=1&t=16469
http://a63.flybb.ru/viewtopic.php?f=16&t=2231
https://love.boltun.su/viewtopic.php?id=6629#p11265
https://nsibirsk.ru/forum/obyavleniya/topic-20903.html