首页 > 其他分享 >17-x龙

17-x龙

时间:2024-02-11 10:11:24浏览次数:28  
标签:17 0x6f9c3c getEncryptContent default 0x1fe1cf var data

day17 x龙

image-20231209163323404

注意:这是其实是一个x美的点选验证。

https://secure.elong.com/passport/login_cn.html?nexturl=https://www.elong.com/
https://www.ishumei.com/trial/captcha.html

1.必备知识点

在开始逆向案例之前,先来学一些前置必备的技能。

1.1 动态无法调试

网页定位到某个js文件中的位置,想要断点调试,但由于每次js的网址都会更新(返回内容虽然一样),导致断点无法执行,这种情况下怎么办?

image-20231209195952627

解决方案:找到返回此js地址的页面,将返回值其替换为固定的值,具体步骤:

  • 安装抓包工具Charles,让Charles代理网页的请求

  • 在Charles中配置,当请求网址时,返回我们指定的HTML数据(网页上加载js地址就是固定的了)

image-20231209200319293

1.Charles安装

image-20231209200907283

注册码
	Registered Name:  https://zhile.io
	License Key:      48891cf209c6d32bf4

2.Charles证书

如果想要让charles抓取浏览器的请求包,必须将charles证书安装到电脑上。

image-20231209201046222

image-20231209201130316

image-20231209201200543

image-20231209201216272

当安装成功后,让charles开始代理电脑的请求(必须勾选上):

image-20231209201331630

image-20231209201456632

3.Charles页面替换

当charles可以作为代理之后,可以在Charles中配置:如果浏览器上访问某网址,给他返回特定的内容。

image-20231209201642818

image-20231209201702350

image-20231209201735667

image-20231209201858049

4.网页访问和调试

image-20231209202018472

1.2 数美示例

https://www.ishumei.com/trial/captcha.html

image-20231209202145331

1.3 文本替换

1.单独替换

image-20231209210833888

image-20231209211046584

import subprocess

res = subprocess.check_output("node part.js 0x8fa")
char_string = res.decode('utf-8').strip()

print(char_string)

2.整体替换

image-20231209214223053

import re
import subprocess


def exec_value(hex_string):
    res = subprocess.check_output(f"node part.js {hex_string}")
    char_string = res.decode('utf-8').strip()
    return char_string


def get_total_set():
    data_set = set()
    with open("v1.js", mode='r', encoding='utf-8') as f1:
        for line in f1:
            if not line:
                continue
            match_list = re.findall(r"var (.*) = _0x2942", line)
            if match_list:
                data_set.add(match_list[0])
    return data_set


def loop_re_match(data_set, line):
    for func in data_set:
        match_list = re.findall(f"({func}\((.*?)\))", line)
        if not match_list:
            continue
        for total, arg in match_list:
            real_value = exec_value(arg)
            line = line.replace(total, f'"{real_value}"')
    return line


def run():
    data_set = get_total_set()
    counter = 0
    with open("v1.js", mode='r', encoding='utf-8') as f1, open("v2.js", mode='w', encoding='utf-8') as f2:
        for line in f1:
            counter += 1
            print(counter)
            if not line:
                f2.write(line)
                continue
            line = loop_re_match(data_set, line)
            f2.write(line)


if __name__ == '__main__':
    run()

2.整体请求分析

https://secure.elong.com/passport/login_cn.html?nexturl=https://www.elong.com/#?

2.1 配置

https://captcha1.fengkongcloud.cn/ca/v1/conf?organization=xQsKB7v2qSFLFxnvmjdO&lang=zh-cn&sdkver=1.1....

image-20231209165518486

image-20231209165702423

2.2 获取验证码

点击登录或验证失败,出现验证码。

https://captcha1.fengkongcloud.cn/ca/v1/register?rversion=1.0.4&model=select&captchaUuid=20231209163703YF....

image-20231209164100950

image-20231209164113846

2.3 提交验证

点击4个验证码后,自动提交。

https://captcha1.fengkongcloud.cn/ca/v2/fverify?qd=tbJHTr9SEFvL5eG%2B...

image-20231209164209714

image-20231209164219774

image-20231209164237791

3.逆向:配置

3.1 请求分析

image-20231209165825888

organization: xQsKB7v2qSFLFxnvmjdO               固定,应该是购买数美产品给的ID
lang: zh-cn
sdkver: 1.1.3
captchaUuid: 20231209163703YFQE2TFkjH2k28pffR    动态,每次刷新不一样【需逆向】
callback: sm_1702111031681
appId: default
model: select                                    应该是验证模式:点选、滑块
channel: DEFAULT
rversion: 1.0.4

image-20231209171001224

image-20231209171059689

image-20231209171149413

image-20231209191954733

'getCaptchaUuid': function _0x2d350e() {
    var _0x49a2b0 = _0x247065
    , _0x1163e8 = ''
    , _0x40ad39 = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"
    , _0x3531f2 = 48;
    for (var _0x235133 = 0; _0x235133 < 18; _0x235133++) {
        _0x1163e8 += _0x40ad39['charAt'](Math['floor'](Math['random']() * _0x3531f2));
    }
    // this[_0x49a2b0(0x232)]()是时间:'20231209202652'   年月日时分秒
    // _0x529f28[_0x49a2b0(0x294)]是个函数,将两个参数拼接起来
    return _0x529f28[_0x49a2b0(0x294)](this[_0x49a2b0(0x232)](), _0x1163e8);
}

所以,CaptchaUuid的生成其实就是:时间 + 随机字符串(例如:"20231209202804MKhadf765PiJNRwYAG")

import random
import datetime

total_string = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"

part = "".join([random.choice(total_string) for i in range(18)])
ctime = datetime.datetime.now().strftime("%Y%m%d%H%M%S")

captcha_uuid = f"{ctime}{part}"
print(captcha_uuid)

3.2 发送请求

image-20231209203721649

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import time

import requests
import random
import datetime

total_string = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"

part = "".join([random.choice(total_string) for i in range(18)])
ctime = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
captcha_uuid = f"{ctime}{part}"

res = requests.get(
    url="https://captcha1.fengkongcloud.cn/ca/v1/conf",
    params={
        "captchaUuid": captcha_uuid,
        "model": "select",
        "appId": "default",
        "rversion": "1.0.4",
        "sdkver": "1.1.3",
        "callback": f"sm_{int(time.time())}",
        "organization": "xQsKB7v2qSFLFxnvmjdO",
        "channel": "DEFAULT",
        "lang": "zh-cn"
    }
)

print(res.text)

4.逆向:获取验证码

image-20231209214646003

image-20231209214658153

无需逆向,直接请求:

image-20231209215305930

import time
import json
import requests
import random
import datetime


def gen_captcha_uuid():
    total_string = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"
    part = "".join([random.choice(total_string) for i in range(18)])
    ctime = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    captcha_uuid = f"{ctime}{part}"
    return captcha_uuid


def run():
    captcha_uuid = gen_captcha_uuid()

    res = requests.get(
        url="https://captcha1.fengkongcloud.cn/ca/v1/register",
        params={
            "captchaUuid": captcha_uuid,
            "model": "select",
            "appId": "default",
            "rversion": "1.0.4",
            "sdkver": "1.1.3",
            "callback": f"sm_callback",
            "organization": "xQsKB7v2qSFLFxnvmjdO",
            "channel": "DEFAULT",
            "lang": "zh-cn",
            "data": "{}"
        }
    )

    data_dict = json.loads(res.text.strip("sm_callback").strip("(").strip(")"))
    print(data_dict)


if __name__ == '__main__':
    run()

5.逆向:提交指纹

https://captcha1.fengkongcloud.cn/ca/v2/fverify
{
    "organization": "xQsKB7v2qSFLFxnvmjdO",
    "tb": "3jSn4gNaAVM%3D",
    "qd": "C0bn16G5KCfL5eG%2BcsX4D2zVk5OJj0v9yp%2FobhabiHPSaRWTFZHwqEESQabyF%2Bifwqvw%2FubnvtTyjoquNCrB09JpFZMVkfCoT3%2BuLCX%2BA5yyOD%2FPFvS%2B51EucGdmqC6MjXlEf1fmqIBRKHWSYzFoY7qCDMDWbBnp0mkVkxWR8Kg1lrHsO5vreY15RH9X5qiA49vPYosSTsacp4eCSlsHhGkN0N7e7dNRy%2BXhvnLF%2BA9BEkGm8hfon663Z%2B5nHqIc",
    "ostype": "web",
    "xy": "YabT6nmJOC0%3D",
    "dy": "Rfpr5oqb5y4%3D",
    "en": "y%2Bugz9NIWys%3D",
    "rversion": "1.0.4",
    "sdkver": "1.1.3",
    "nu": "C0kH%2FbWLjw8%3D",
    "mu": "8LlSNopqsqQ97nxg3tdIZ6XUAHUd9ZXRv1tmsN36Xi4fSW76upB2jGHRX517zf%2Bb%2Bc1ERqOHAyjeY2BarYayPR9Jbvq6kHaMYZZm%2FG7Dqv70lf%2BNyEwjJti6s6NuBhD3X3AJlc40WilxEDbwniT602l%2BVxoLs71sH0lu%2BrqQdozaurDbYgC84l9wCZXONFopdgBBtTGOrxytgM41ZQbVteQJ8SC%2F%2FSvbPe58YN7XSGdh0V%2Bde83%2Fm7RQACrkbzLP",
    "captchaUuid": "20231209214506ff2iMsMEYx24tjQdzD",
    "jo": "l3aEINYnwpY%3D",
    "act.os": "web_pc",
    "protocol": "180",
    "rid": "20231209214510fa3c46caca0a508076",
    "callback": "sm_1702130040080",
    "mp": "WYfkIZp7GoA%3D",
    "oc": "h9oFKi8cHpg%3D",
    "ww": "jYqq0U9Wjos%3D",
    "kq": "mtlOTdT5LOE%3D"
}

image-20231209215414182

5.1 定位

当在图片上点击第4标记时,触发请求。

image-20231209215801958

image-20231209113911373

image-20231209114058719

5.2 特殊替换

image-20231209220738504

import re
import subprocess


def exec_value(hex_string):
    res = subprocess.check_output(f"node part.js {hex_string}")
    char_string = res.decode('utf-8').strip()
    return char_string


def run():
    counter = 0
    with open("v2.js", mode='r', encoding='utf-8') as f1, open("v3.js", mode='w', encoding='utf-8') as f2:
        for line in f1:
            counter += 1
            print(counter)
            if not line:
                f2.write(line)
                continue

            match_list = re.findall(r"(_0x2cd1cf\((.*?)\))", line)
            for total, arg in match_list:
                real_value = exec_value(arg)
                line = line.replace(total, f'"{real_value}"')
            f2.write(line)


if __name__ == '__main__':
    run()

5.3 第4次请求

image-20231209221130771

image-20231209221607289

image-20231209221538368

var _0x2c1c24 = _0x3d50b3, _0x2d6657 = {
                    'nyzWi': "mouseMoveX",
                    'SZdlb': _0x6f9c3c["pzOLX"],
                    'TGQiS': function(_0x569f5d, _0x276cc4) {
                        var _0x5d1724 = _0x2c1c24;
                        return _0x6f9c3c["onIeC"](_0x569f5d, _0x276cc4);
                    }
                }, _0x1fe1cf, _0x44bcff = this["_config"], _0x599170 = _0x44bcff['domains'], _0x3d6fed = _0x44bcff["fVerifyUrlV2"], _0x1ed2cb = _0x3d6fed === undefined ? _0x3b4628 : _0x3d6fed, _0x49bb92 = _0x44bcff["organization"], _0x20df23 = _0x44bcff["appId"], _0x4143cf = _0x44bcff["channel"], _0x435e2e = _0x44bcff['VERSION'], _0x7306d7 = _0x44bcff['lang'], _0x294474 = _0x44bcff["SDKVER"], _0x1b27c8 = _0x44bcff["_successCallback"], _0x2d5257 = _0x44bcff["mode"], _0x5b58d1 = this['_data'], _0x536387 = _0x5b58d1["errMsg"], _0x74fdb5 = _0x5b58d1["trueWidth"], _0x31e834 = _0x6f9c3c["tIGQt"](_0x74fdb5, undefined) ? -0x1338 + -0x145f + -0x7eb * -0x5 : _0x74fdb5, _0x3717b1 = this["getRegisterData"](_0x6f9c3c["ZGnpS"]), _0x298b01 = this["getMouseAction"](), _0x528bd = _0x6f9c3c["VQpVP"], _0x49f479 = this["getSafeParams"](), _0x28800d = _0x2460cd[_0x6f9c3c["Nlbsb"]]["extend"]((_0x1fe1cf = {
                    'organization': _0x49bb92
                },
                (-0x1be8 + 0x20f2 + -0x50a,
                _0x1f0d12[_0x6f9c3c["Nlbsb"]])(_0x1fe1cf, 'mp', this['getEncryptContent'](_0x20df23, _0x6f9c3c["NnjXa"])),
                (0xd * 0x103 + 0x2 * 0x3a + 0x2b * -0x51,
                _0x1f0d12[_0x6f9c3c["Nlbsb"]])(_0x1fe1cf, 'oc', this["getEncryptContent"](_0x4143cf, "c2659527")),
                (-0x81e + -0x166 * 0x10 + -0x6 * -0x515,
                _0x1f0d12['default'])(_0x1fe1cf, 'xy', this["getEncryptContent"](_0x7306d7, _0x6f9c3c["wIUSM"])),
                (-0x10a5 + -0xafb + -0x2 * -0xdd0,
                _0x1f0d12["default"])(_0x1fe1cf, 'jo', this["getEncryptContent"](_0x49f479, _0x6f9c3c["HwXkn"])),
                (-0x279 * 0x1 + -0xa9 * 0x10 + 0xd09,
                _0x1f0d12[_0x6f9c3c["Nlbsb"]])(_0x1fe1cf, _0x6f9c3c["ZGnpS"], _0x3717b1),
                (-0xf82 + -0x3ee + -0x10 * -0x137,
                _0x1f0d12[_0x6f9c3c["Nlbsb"]])(_0x1fe1cf, _0x6f9c3c["CJfJJ"], _0x435e2e),
                (-0x2 * 0xf16 + 0x1d69 + 0xc3,
                _0x1f0d12[_0x6f9c3c['Nlbsb']])(_0x1fe1cf, _0x6f9c3c["WWhmm"], _0x294474),
                (-0x18 * -0x60 + 0x1 * 0x1147 + -0x1a47,
                _0x1f0d12[_0x6f9c3c['Nlbsb']])(_0x1fe1cf, _0x6f9c3c["VRUfH"], "180"),
                (-0x14 * -0x22 + 0x53 * -0x75 + -0xb * -0x335,
                _0x1f0d12[_0x6f9c3c["Nlbsb"]])(_0x1fe1cf, "ostype", _0x528bd),
                _0x1fe1cf), _0x298b01);
                _0x2460cd["default"]["log"](_0x119bdb["LOG_ACTION"]["SEND_VERIFY"]),
                this["sendRequest"](_0x5c100a, _0x599170, _0x1ed2cb, _0x28800d, _0x5c6636, _0x4d541b);
var _0x2c1c24 = _0x3d50b3;
var _0x2d6657 = {
    'nyzWi': "mouseMoveX",
    'SZdlb': 'mouseEndX',
    'TGQiS': function(_0x569f5d, _0x276cc4) {
        var _0x5d1724 = _0x2c1c24;
        // _0x6f9c3c["onIeC"]用于判断两个值是否相等,内部:return  _0x569f5d != _0x569f5d;
        return _0x6f9c3c["onIeC"](_0x569f5d, _0x569f5d);
    }
};
var _0x1fe1cf;
var _0x44bcff = this["_config"];
var _0x599170 = _0x44bcff['domains']
var _0x3d6fed = _0x44bcff["fVerifyUrlV2"];
var _0x1ed2cb = _0x3d6fed === undefined ? _0x3b4628 : _0x3d6fed;
var _0x49bb92 = _0x44bcff["organization"];
var _0x20df23 = _0x44bcff["appId"];
var _0x4143cf = _0x44bcff["channel"];
var _0x435e2e = _0x44bcff['VERSION'];
var _0x7306d7 = _0x44bcff['lang'];
var _0x294474 = _0x44bcff["SDKVER"];
var _0x1b27c8 = _0x44bcff["_successCallback"];
var _0x2d5257 = _0x44bcff["mode"]; 
var _0x5b58d1 = this['_data'];
var _0x536387 = _0x5b58d1["errMsg"];
var _0x74fdb5 = _0x5b58d1["trueWidth"];
var _0x31e834 = _0x6f9c3c["tIGQt"](_0x74fdb5, undefined) ? -0x1338 + -0x145f + -0x7eb * -0x5 : _0x74fdb5;
var _0x3717b1 = this["getRegisterData"]("rid");
// {"qd":"JMTaZ9mhBafL5eG+csX4DyykOT58z4NkvEylF9PEJf7L5eG+csX4D99Jw/pxKLyZXDGGiPXI3qKOTQVxDcRTT8vl4b5yxfgPCOtHpI8vokEhvIaN1eVD3J/VbVfaGFhTe2zsfSFY0HHL5eG+csX4D2zVk5OJj0v9aN98yIZfXFAFVo8CcJXqET4/o/Xm4TM8IqH+lfRKs4nL5eG+csX4D0nKUbNy9O0wcJHXvw3IxzW+4fUG0TddNA==","mu":"pVx74d45J5Q97nxg3tdIZ/0y4ytD9A8okEmxa7DPFbc97nxg3tdIZ0Iv9A4eLBoHN4bO+sawYrQ4vvTzU3B2tD3ufGDe10hn6YzZYianpoalBvuJfSGuOVhLRHjg97pupzIANY/0R2A97nxg3tdIZ6XUAHUd9ZXR6MGQBiNUcNNyI8hU9pnNqbePBqpC9rB5D36gNvYfg2E97nxg3tdIZ3QQRwUdc7LcgEXxAfwdlAw77pLU1Nb2VA==","ww":"PN3c45aazWY=","nu":"C0kH/bWLjw8=","dy":"Rfpr5oqb5y4=","act.os":"web_pc","tb":"3jSn4gNaAVM=","en":"y+ugz9NIWys=","kq":"mtlOTdT5LOE="}
var _0x298b01 = this["getMouseAction"]();
var _0x528bd = _0x6f9c3c["VQpVP"];
var _0x49f479 = this["getSafeParams"]();

// _0x28800d = _0x298b01中的键值对 + _0x1fe1cf中的键值对
var _0x28800d = _0x2460cd["default"]["extend"](
    (
        var _0x1fe1cf = {
            'organization': _0x49bb92
        };
        _0x1f0d12['default'])(_0x1fe1cf, 'mp', this['getEncryptContent']('default', '9cc268c1');
		_0x1f0d12['default'])(_0x1fe1cf, 'oc', this["getEncryptContent"]('DEFAULT', "c2659527");
		_0x1f0d12['default'])(_0x1fe1cf, 'xy', this["getEncryptContent"]('zh-cn', 'b1807581');
		_0x1f0d12["default"])(_0x1fe1cf, 'jo', this["getEncryptContent"]("11", '6d005958);
		_0x1f0d12['default'])(_0x1fe1cf, 'rid', _0x3717b1);
		_0x1f0d12['default'])(_0x1fe1cf, 'rversion', _0x435e2e);
		_0x1f0d12['default'])(_0x1fe1cf, 'sdkver', _0x294474);
		_0x1f0d12['default'])(_0x1fe1cf, 'protocol', "180");
		_0x1f0d12['default'])(_0x1fe1cf, "ostype", _0x528bd);
		_0x1fe1cf
    ), 
    _0x298b01 
);

_0x2460cd["default"]["log"]('sendVerify');
this["sendRequest"](_0x5c100a, _0x599170, _0x1ed2cb, _0x28800d, _0x5c6636, _0x4d541b);

5.4 getEncryptContent

image-20231209223710605

image-20231209223844708

_0x379d2f['prototype'][_0x3d50b3(0x553)] = function _0x2fa022(_0x116ae9, _0x14175b) {
    var _0x52262 = _0x3d50b3
    , _0x12a32c = this["_data"]['__key']
    , _0xcaece9 = _0x14175b || _0x12a32c;
    _0x2460cd["default"]["isJsFormat"]() && (_0xcaece9 = _0x3212f5);
    var _0x376380 = _0x6f9c3c['abzhI'](typeof _0x116ae9, _0x6f9c3c['hOuuf']) ? !![] : ![]
    , _0x7c7833 = _0x376380 ? _0x116ae9 : _0x2460cd[_0x6f9c3c['Nlbsb']]['smStringify'](_0x116ae9)
    , _0x197249 = '';
    return _0x197249 = _0x21c59e["default"]["DES"](_0xcaece9, _0x7c7833, 0x2 * 0x96b + 0x376 * -0x1 + -0xf5f, 0x177d + 0xa10 * -0x3 + 0x6b3),
        _0x197249 = _0x21c59e['default']["base64Encode"](_0x197249),
        _0x197249;
}
### 断点重大发现执行多次,猜想参数的生成也会走这里 ### 

function _0x2fa022(_0x116ae9, _0x14175b) {
    var _0x12a32c = this["_data"]['__key'];
    var _0xcaece9 = _0x14175b || _0x12a32c; // 如果函数第二个参数值为第二个参数,否则: this["_data"]['__key'];
    
    // _0x3212f5 = "1702133023321.823ishumei.com"   -> 时间戳 + "ishumei.com"
    _0x2460cd["default"]["isJsFormat"]() && (_0xcaece9 = _0x3212f5);
    
    // _0x376380 = false
    var _0x376380 = _0x6f9c3c['abzhI'](typeof _0x116ae9, _0x6f9c3c['hOuuf']) ? !![] : ![];
    
    // 对第1个参数进行序列化
    var  _0x7c7833 = _0x376380 ? _0x116ae9 : _0x2460cd[_0x6f9c3c['Nlbsb']]['smStringify'](_0x116ae9);
    var  _0x197249 = '';
    
    // DES加密( 参数2或this["_data"]['__key'],    参数1序列化,   1,   0  )
    _0x197249 = _0x21c59e["default"]["DES"](_0xcaece9, _0x7c7833, 1, 0);
    // base64编码
	_0x197249 = _0x21c59e['default']["base64Encode"](_0x197249);
	return _0x197249;
}
'ZtRQP': function(_0xb10f2e, _0x58b43b) {
    return _0xb10f2e + _0x58b43b;
}
// Math["random"](), +new Date()  ---> 1702133279842
_0x3212f5 = _0x1d0195['ZtRQP'](   _0x1d0195["ZtRQP"](  Math["random"](), +new Date()  )    , "ishumei.com")

5.5 DES加密

1.node

npm install crypto-js -g
var CryptoJS = require("crypto-js")

function DESEncrypt(key, word) {
    var key_ = CryptoJS.enc.Utf8.parse(key);
    var srcs = CryptoJS.enc.Utf8.parse(word);
    var encrypted = CryptoJS.DES.encrypt(srcs, key_, {
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.ZeroPadding
    });
    return encrypted.toString();
}

function DESDecrypt(key, word) {
    var key_ = CryptoJS.enc.Utf8.parse(key);
    var decrypt = CryptoJS.DES.decrypt(word, key_, {
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.ZeroPadding
    });
    return decrypt.toString(CryptoJS.enc.Utf8);
}


var result = DESEncrypt("6f5e9847","1");
console.log(result);

2.Python

import base64
from Crypto.Cipher import DES


key = b'6f5e9847'
data_string = '1'

pad_func = lambda text: text + '\0' * (DES.block_size - (len(text.encode('utf-8')) % DES.block_size))
aes = DES.new(key, DES.MODE_ECB)
enc_data = aes.encrypt(pad_func(data_string).encode("utf8"))
res = base64.b64encode(enc_data).decode('utf-8')
print(res)
from Crypto.Cipher import DES
import base64


class EncryptDate:
    def __init__(self, key):
        self.key = key  # 初始化密钥
        self.length = DES.block_size  # 初始化数据块大小
        self.aes = DES.new(self.key, DES.MODE_ECB)  # 初始化AES,ECB模式的实例
        # 截断函数,去除填充的字符
        self.unpad = lambda date: date[0:-ord(date[-1])]

    def pad(self, text):
        """
        #填充函数,使被加密数据的字节码长度是block_size的整数倍
        """
        count = len(text.encode('utf-8'))
        add = self.length - (count % self.length)
        entext = text + '\0' * add
        return entext

    def encrypt(self, encrData):  # 加密函数
        res = self.aes.encrypt(self.pad(encrData).encode("utf8"))
        msg = str(base64.b64encode(res), encoding="utf8")
        return msg

    def decrypt(self, decrData):  # 解密函数
        res = base64.decodebytes(decrData.encode("utf8"))
        msg = self.aes.decrypt(res).decode("utf8")
        return self.unpad(msg)


if __name__ == '__main__':
    cr = EncryptDate(b'6f5e9847')
    cr_res = cr.encrypt('1')
    print(cr_res)

5.6 意外收获-其他参数

在调试getEncryptContent时,发现除了 _0x298b01 相关的参数也走了 getEncryptContent

// _0x28800d = _0x298b01中的键值对 + _0x1fe1cf中的键值对
var _0x28800d = _0x2460cd["default"]["extend"](
    (
        var _0x1fe1cf = {
            'organization': _0x49bb92
        };
        _0x1f0d12['default'])(_0x1fe1cf, 'mp', this['getEncryptContent']('default', '9cc268c1');
		_0x1f0d12['default'])(_0x1fe1cf, 'oc', this["getEncryptContent"]('DEFAULT', "c2659527");
		_0x1f0d12['default'])(_0x1fe1cf, 'xy', this["getEncryptContent"]('zh-cn', 'b1807581');
		_0x1f0d12["default"])(_0x1fe1cf, 'jo', this["getEncryptContent"]("11", '6d005958);
                                                                         
		_0x1f0d12['default'])(_0x1fe1cf, 'rid', _0x3717b1);
		_0x1f0d12['default'])(_0x1fe1cf, 'rversion', _0x435e2e);
		_0x1f0d12['default'])(_0x1fe1cf, 'sdkver', _0x294474);
		_0x1f0d12['default'])(_0x1fe1cf, 'protocol', "180");
		_0x1f0d12['default'])(_0x1fe1cf, "ostype", _0x528bd);
		_0x1fe1cf
    ), 
    _0x298b01 
);
{
	
    "mp":"WYfkIZp7GoA=",
    "oc":"h9oFKi8cHpg=",
    "xy":"YabT6nmJOC0=",
    "jo":"l3aEINYnwpY=",
    
    "qd":"iUUzUpbSrVLL5eG+cs....",
    "mu":"Gcxg3RiVeoM97nxg3tdIZ3nzK3R7...",
    "ww":"iLL8K/VHX50=",
    "nu":"C0kH/bWLjw8=",
    "dy":"Rfpr5oqb5y4=",
    "tb":"3jSn4gNaAVM=",
    "en":"y+ugz9NIWys=",
    "kq":"mtlOTdT5LOE=",
    
	"rid":"2023121111394173034a940e0c5d82cc",
    "organization":"xQsKB7v2qSFLFxnvmjdO",
    "rversion":"1.0.4",
    "sdkver":"1.1.3",
    "protocol":"180",
    "ostype":"web",
    "callback":"随便写",
	"act.os":"web_pc",
    "captchaUuid":"20231211113934kRf2pzsG3e7ACCJzDt"
}

image-20231211115755335

5.7 替换再观察

image-20231211120845915

image-20231211120908262

_0x379d2f[_0x3d50b3(0x7c3)][_0x3d50b3(0x8b6)] = function _0x472cf7() {
    var _0x425d8a = _0x3d50b3
    , _0x59854a = this["_config"]["mode"]
    , _0xd2bf45 = this['getRegisterData']()
    , _0x18bde8 = _0xd2bf45['k']
    , _0x10af77 = _0xd2bf45['l']
    , _0x2dd8cf = _0x21c59e["default"]["base64Decode"](_0x18bde8)
    , _0x320ed7 = _0x21c59e[_0x6f9c3c["Nlbsb"]]["DES"](_0x913fc8, _0x2dd8cf, -0x8d6 + -0x2 * -0x347 + 0x248, -0x23c1 + 0x158 * 0x5 + 0x1 * 0x1d09)["substr"](-0xc * -0x206 + 0x118d + -0x29d5, _0x10af77)
    , _0x4fc323 = this['_data']
    , _0x3602ea = _0x4fc323['mouseData']
    , _0x5caf5a = _0x4fc323["startTime"]
    , _0x53f1f2 = _0x4fc323['endTime']
    , _0x16f5e6 = _0x4fc323['mouseEndX']
    , _0x4c1632 = _0x4fc323["trueWidth"]
    , _0x1a7546 = _0x4fc323["trueHeight"]
    , _0x23de95 = _0x4fc323["selectData"]
    , _0x15fb04 = _0x4fc323["blockWidth"]
    , _0x46483a = this['getOs']()
    , _0x4639e5 = {}
    , _0x4a85ac = {};
    switch (_0x59854a) {
        case _0x6f9c3c["rbFkk"]:
        case _0x6f9c3c["ClrAc"]:
        case _0x6f9c3c["XOdyC"]:
        case "spatial_select":
            _0x4639e5['qd'] = this["getEncryptContent"](_0x23de95, _0x6f9c3c['lKTut']),
                _0x4639e5['mu'] = this['getEncryptContent'](_0x3602ea, "e7e1eb0d"),
                _0x4639e5['ww'] = this["getEncryptContent"](_0x6f9c3c["pxDrO"](_0x53f1f2, _0x5caf5a), '17a94a08'),
                _0x4639e5['nu'] = this['getEncryptContent'](_0x4c1632, "390aac0d"),
                _0x4639e5['dy'] = this["getEncryptContent"](_0x1a7546, "a9001672"),
                _0x4639e5["act.os"] = _0x46483a;
            break;
        case _0x6f9c3c['JGZBJ']:
            _0x4639e5['je'] = this["getEncryptContent"](_0x16f5e6 / _0x4c1632, _0x6f9c3c["CvUZE"]),
                _0x4639e5['mu'] = this["getEncryptContent"](_0x3602ea, _0x6f9c3c["LCDQP"]),
                _0x4639e5['ww'] = this["getEncryptContent"](_0x53f1f2 - _0x5caf5a, '17a94a08'),
                _0x4639e5['nu'] = this['getEncryptContent'](_0x4c1632, _0x6f9c3c["naQWg"]),
                _0x4639e5['dy'] = this["getEncryptContent"](_0x1a7546, _0x6f9c3c["aBnmb"]),
                _0x4639e5[_0x6f9c3c["rhyQb"]] = _0x46483a;
            _0x6f9c3c["sxkJS"](_0x4c1632, -0xf8e + 0xc32 * -0x2 + -0x1 * -0x27f2) && (_0x4639e5['je'] = this["getEncryptContent"](0x118f + 0xb * -0xa + -0x1121, _0x6f9c3c['CvUZE']));
            break;
        case "auto_slide":
            _0x4639e5['je'] = this["getEncryptContent"](_0x6f9c3c['KrcnC'](_0x16f5e6, _0x6f9c3c["pxDrO"](_0x4c1632, _0x15fb04)), "5ea96022"),
                _0x4639e5['mu'] = this["getEncryptContent"](_0x3602ea, _0x6f9c3c['LCDQP']),
                _0x4639e5['ww'] = this["getEncryptContent"](_0x53f1f2 - _0x5caf5a, _0x6f9c3c["rOONd"]),
                _0x4639e5['nu'] = this['getEncryptContent'](_0x4c1632, "390aac0d"),
                _0x4639e5['dy'] = this["getEncryptContent"](_0x1a7546, 'a9001672'),
                _0x4639e5[_0x6f9c3c["rhyQb"]] = _0x46483a;
            break;
    }
    return _0x4639e5['tb'] = this["getEncryptContent"](_0x2460cd[_0x6f9c3c["Nlbsb"]]["__userConf"]["console"], '6f5e9847'),
        _0x4639e5['en'] = this["getEncryptContent"](_0x2460cd[_0x6f9c3c["Nlbsb"]]["runBotDetection"](), "9fc1337f"),
        _0x4639e5['kq'] = this["getEncryptContent"](-(0x7 * -0x537 + -0x1f77 + 0x1 * 0x43f9), _0x6f9c3c['SGQFW']),
        this["_data"][_0x6f9c3c["KXgnx"]] = _0x320ed7,
        _0x4639e5;
}

分析参数值:

	_0x4639e5['qd'] = this["getEncryptContent"](this['_data']["selectData"],'3c9ed5cb'),
    _0x4639e5['mu'] = this['getEncryptContent'](this['_data']['mouseData'], "e7e1eb0d"),
    _0x4639e5['ww'] = this["getEncryptContent"](
        _0x6f9c3c["pxDrO"](this['_data']['endTime'], this['_data']["startTime"]),   // 函数=第1个值-第2个值
        '17a94a08'
    ),
    _0x4639e5['nu'] = this['getEncryptContent'](this['_data']["trueWidth"], "390aac0d"),
    _0x4639e5['dy'] = this["getEncryptContent"](this['_data']["trueHeight"], "a9001672"),
    _0x4639e5["act.os"] = _0x46483a;
    //_0x4639e5['tb'] = this["getEncryptContent"](_0x2460cd['default']["__userConf"]["console"], '6f5e9847'),
    _0x4639e5['tb'] = this["getEncryptContent"](1, '6f5e9847'),
    
    //_0x4639e5['en'] = this["getEncryptContent"](_0x2460cd['default']["runBotDetection"](), "9fc1337f"),
	_0x4639e5['en'] = this["getEncryptContent"](0, "9fc1337f"),
        
    _0x4639e5['kq'] = this["getEncryptContent"](-1, 'ebee8dcc'),

image-20231211115246178

image-20231211115348525

image-20231211115408675

image-20231211115438559

image-20231211115455664

image-20231211115508613

image-20231211115605510

image-20231211115550241

5.8 selectData

接下来就是搞定:this['_data']["selectData"]或 this['_data']['mouseData'] 两个值在点选时是一样的。

在刚开始定位有相关赋值:

image-20231211123627668

image-20231211123649248

image-20231211123702464

这其实是 mousedown事件,每次点击都会执行,在这里生成相应的数据:

image-20231211123851343

image-20231211124110988

5.9 单步调试

image-20231211124806754

image-20231211125404727

_0x52aa32 = +new Date()

_0x27bb65 = [_0x42ca39,  _0x1d0195["IXgiz"](_0x28ed43['y'], _0x1e9886) / _0x30fbb2,    _0x52aa32];

1.第1个值

_0x27bb65 = [_0x42ca39,  _0x1d0195["IXgiz"](_0x28ed43['y'], _0x1e9886) / _0x30fbb2,    _0x52aa32];
// _0x1d0195["rlnuS"]函数是:第1个参数/第2个参数
// _0x28ed43['x']是鼠标横坐标,事件.pageX
// _0x5a1fbf是图片距可视窗口的left距离
// _0xd3e4df是div图片真实大小300
_0x42ca39 = _0x1d0195["rlnuS"](_0x28ed43['x'] - _0x5a1fbf, _0xd3e4df);
_0x289f0f = event事件
_0x28ed43 = this['getMousePos'](_0x289f0f)   // 读取鼠标位置:pageX、pageY


_0x4351e1 = _0x2460cd['default']['getBoundingClientRect'](_0x170354)  // _0x170354是图片div的DOM对象,距离可视窗口的距离
_0x5a1fbf = _0x4351e1['x']   // 图片div的DOM对象left距离可是窗口left距离

_0xd3e4df = _0x311abd['trueWidth']  // div图片真实大小300


// _0x1d0195["rlnuS"]函数是:第1个参数/第2个参数
// _0x28ed43['x']是鼠标横坐标,事件.pageX
// _0x5a1fbf是图片距可视窗口的left距离
// _0xd3e4df是div图片真实大小300
_0x42ca39 = _0x1d0195["rlnuS"](_0x28ed43['x'] - _0x5a1fbf, _0xd3e4df);
'getBoundingClientRect': function _0x5322a8(_0x1e09b6) {
    var _0x4dc5c0 = _0x177388
    // _0x1e09b6是图片div的DOM对象
    // getBoundingClientRect是Javascript中的一种方法,用于获取一个元素相对于视口的位置和大小
    , _0x5bf4ef = _0x1e09b6['getBoundingClientRect']()
    // 滚动距离,可以为0
    , _0x462c70 = _0x3d223c["documentElement"]["scrollLeft"] ? _0x3d223c['documentElement']["scrollLeft"] : _0x3d223c['body']['scrollLeft']
    // 滚动距离,可以为0
    , _0x82d331 = _0x3d223c['documentElement']["scrollTop"] ? _0x3d223c["documentElement"]["scrollTop"] : _0x3d223c["body"]["scrollTop"];
    return {
        'x': _0x5bf4ef["left"] + _0x462c70,
        'y': _0x5bf4ef["top"] + _0x82d331
    };
}

image-20231211131301195

image-20231211131634191

3.第2个值

_0x27bb65 = [_0x42ca39,  _0x1d0195["IXgiz"](_0x28ed43['y'], _0x1e9886) / _0x30fbb2,    _0x52aa32];
_0x1d0195["IXgiz"](_0x28ed43['y'], _0x1e9886) / _0x30fbb2
 _0x28ed43 = this['getMousePos'](_0x289f0f)
_0x28ed43['y']  // 鼠标的y轴距离

_0x4351e1 = _0x2460cd['default']['getBoundingClientRect'](_0x170354)  // _0x170354是图片div的DOM对象,距离可视窗口的距离
 _0x1e9886 = _0x4351e1['y']  // 图片div的DOM对象距离可视窗口top的距离。

_0x30fbb2 = _0x311abd["trueHeight"]  // 图片真实宽度 150

_0x1d0195["IXgiz"](_0x28ed43['y'], _0x1e9886) / _0x30fbb2
0.11666666666666667  * 300
0.7633333333333333 * 150

5.10 第4次点击

image-20231211125040751

image-20231209113911373

image-20231209114058719

6.识别和测试

6.1 验证码识别

import base64
import requests
from hashlib import md5

file_bytes = open('code.jpg', 'rb').read()

res = requests.post(
    url='http://upload.chaojiying.net/Upload/Processing.php',
    data={
        'user': "wupeiqi",
        'pass2': md5("密码".encode('utf-8')).hexdigest(),
        'codetype': "9501",
        'file_base64': base64.b64encode(file_bytes)
    },
    headers={
        'Connection': 'Keep-Alive',
        'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
    }
)

res_dict = res.json()
# {'err_no': 0, 'err_str': 'OK', 'pic_id': '1234612060701120002', 'pic_str': '的,86,73|粉,111,38|菜,40,49|香,198,101', 'md5': 'faac71fc832b2ead01ffb4e813f3be60'}

# 8.每个字的坐标  {"鸭":(196,85), ...}    target_word_list = ["花","鸭","字"]
bg_word_dict = {}
for item in res_dict["pic_str"].split("|"):
    word, x, y = item.split(",")
    bg_word_dict[word] = (x, y)

6.2 提交点选【搞定】

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import time
import json
import requests
import random
import datetime
import base64
import requests
from hashlib import md5
import base64
from Crypto.Cipher import DES


def gen_captcha_uuid():
    total_string = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"
    part = "".join([random.choice(total_string) for i in range(18)])
    ctime = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    captcha_uuid = f"{ctime}{part}"
    return captcha_uuid


def register(captcha_uuid):
    res = requests.get(
        url="https://captcha1.fengkongcloud.cn/ca/v1/register",
        params={
            "captchaUuid": captcha_uuid,
            "model": "select",
            "appId": "default",
            "rversion": "1.0.4",
            "sdkver": "1.1.3",
            "callback": f"sm_callback",
            "organization": "xQsKB7v2qSFLFxnvmjdO",
            "channel": "DEFAULT",
            "lang": "zh-cn",
            "data": "{}"
        }
    )

    data_dict = json.loads(res.text.strip("sm_callback").strip("(").strip(")"))
    return data_dict


def image_ocr(file_bytes):
    res = requests.post(
        url='http://upload.chaojiying.net/Upload/Processing.php',
        data={
            'user': "wupeiqi",
            'pass2': md5("密码".encode('utf-8')).hexdigest(),
            'codetype': "9501",
            'file_base64': base64.b64encode(file_bytes)
        },
        headers={
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }
    )

    res_dict = res.json()
    # {'err_no': 0, 'err_str': 'OK', 'pic_id': '1234612060701120002', 'pic_str': '的,86,73|粉,111,38|菜,40,49|香,198,101', 'md5': 'faac71fc832b2ead01ffb4e813f3be60'}

    # 8.每个字的坐标  {"鸭":(196,85), ...}    target_word_list = ["花","鸭","字"]
    bg_word_dict = {}
    for item in res_dict["pic_str"].split("|"):
        word, x, y = item.split(",")
        bg_word_dict[word] = (int(x), int(y))
    return bg_word_dict


def des_encrypt(data_string, key):
    pad_func = lambda text: text + '\0' * (DES.block_size - (len(text.encode('utf-8')) % DES.block_size))
    aes = DES.new(key.encode('utf-8'), DES.MODE_ECB)
    enc_data = aes.encrypt(pad_func(data_string).encode("utf8"))
    res = base64.b64encode(enc_data).decode('utf-8')
    return res


def run():
    captcha_uuid = gen_captcha_uuid()

    # 注册请求
    data_dict = register(captcha_uuid)

    # 获取图片和识别的文字
    bg_img_url = f'https://castatic.fengkongcloud.cn{data_dict["detail"]["bg"]}'
    char_list = data_dict["detail"]['order']
    rid = data_dict["detail"]['rid']
    print("图上文字验证码:", char_list)

    res = requests.get(url=bg_img_url).content
    ocr_word_dict = image_ocr(res)
    print("识别的验证码:", ocr_word_dict)

    char_list.reverse()
    select_data = []
    for idx, char in enumerate(char_list, 1):
        group = ocr_word_dict.get(char)
        if not group:
            continue
        x, y = group
        real_x = x / 2 / 300
        real_y = y / 2 / 150
        ctime = int((time.time() - random.uniform(idx - 1, idx) * 1000))
        select_data.insert(0, [real_x, real_y, ctime])
        time.sleep(1)

    print("构造点选坐标:", select_data)

    select_data_string = json.dumps(select_data, separators=(',', ':'))
    start_time = select_data[0][-1]
    end_time = int(time.time() * 1000)

    mapping = {
        "mp": ('default', '9cc268c1'),
        "oc": ('DEFAULT', "c2659527"),
        "xy": ('zh-cn', 'b1807581'),
        "jo": ("11", '6d005958'),
        "qd": (select_data_string, '3c9ed5cb'),
        "mu": (select_data_string, "e7e1eb0d"),
        "ww": (str(end_time - start_time), '17a94a08'),
        "nu": ("300", "390aac0d"),
        "dy": ("150", "a9001672"),
        "tb": ("1", '6f5e9847'),
        "en": ("0", "9fc1337f"),
        "kq": ("-1", 'ebee8dcc'),
    }

    verify_dict = {
        "rid": rid,
        "organization": "xQsKB7v2qSFLFxnvmjdO",
        "rversion": "1.0.4",
        "sdkver": "1.1.3",
        "protocol": "180",
        "ostype": "web",
        "callback": "func_callback",
        "act.os": "web_pc",
        "captchaUuid": captcha_uuid
    }
    for k, group in mapping.items():
        data_string, key = group
        enc = des_encrypt(data_string, key)
        verify_dict[k] = enc

    res = requests.get(
        url="https://captcha1.fengkongcloud.cn/ca/v2/fverify",
        params=verify_dict,
        headers={
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
            "Referer": "https://secure.elong.com/",
            "Host": "captcha1.fengkongcloud.cn",
            "Origin": "https://secure.elong.com",

        }
    )
    print("识别结果:", res.text)


if __name__ == '__main__':
    run()

image-20231211163143838

7.登录

点选搞定之后,提交登录就简单了,直接携带 rid发送请求就行了。

image-20231211164411880

image-20231211164639793

标签:17,0x6f9c3c,getEncryptContent,default,0x1fe1cf,var,data
From: https://www.cnblogs.com/fuminer/p/18013197

相关文章

  • POJ--1179 Polygon(区间DP)
    记录22:012024-2-10http://poj.org/problem?id=1179区间DP问题。区间DP问题可能需要注意的点就是是根据区间长度来计算的,随着迭代区间长度不断增加,结果也就计算出来了这种“任意选择一个位置断开,复制形成2倍长度的链”的方法,是解决DP中环形结构的常用手段之一因此读入数......
  • P4090 [USACO17DEC] Greedy Gift Takers P
    原题链接题解1.如果前\(7\)头牛能全部能拿到礼物,但是这前\(7\)头牛里有\(4\)头牛更新在前\(4\)的位置,请问第\(8\)头牛能否得到礼物?答案是不行,因为前\(4\)头牛会在前\(4\)的位置形成循环2.假如恰好第\(x\)头牛没有礼物,那么牛\(x\)之后的牛都得不到礼物,因为不......
  • QOJ 8171 - Cola
    我们假设目前B知道排列\(p\)的前\(x\)位是多少,那么下一次,B的最优策略是:对于\(i\lex\)的部分,令\(q_i=p_i\)。对于\(i=x+1\)的部分,令\(q_i\)为任一\(p_i\)可能取到但没有被猜过的值。对于\(i>x+1\),随机乱排剩余的\(q_i\)。考虑设\(c_i\)表示第\(i\)次......
  • P9170 [省选联考 2023] 填数游戏 题解
    Description众所周知,Alice和Bob是一对好朋友。今天,他们约好一起玩游戏。一开始,他们各自有一张空白的纸条。接下来,他们会在纸条上依次写\(n\)个\([1,m]\)范围内的正整数。等Alice写完,Bob在看到Alice写的纸条之后开始写他的纸条。Alice需要保证她写下的第\(i\)个......
  • CF1771F Hossam and Range Minimum Query 题解
    题目链接:CF或者洛谷比较不错的题,出现奇数次出现的这种问题,比较容易想到一种运算,那就是异或和运算。如果一个区间上一个数出现偶数次,则它对于异或和的贡献为\(0\),那么很显然,我们维护下区间异或和即可判断一个区间上是否存在出现奇数次的数。然后我们注意到\(1\oplus2\oplu......
  • CF1706E 题解
    你谷题目传送门CF题目传送门题目大意给定一个\(n\)个点\(m\)条边的无向图,询问\(q\)次,每次询问会指定两个正整数\(l,r\),问要使对于\(l\leqa\leqb\leqr\)的所有\(a,b\)均有路径可以互相到达,最少需要加入前多少条边。思路考虑到每一次询问实质上就是问你在按......
  • CF1735D Meta-set 题解
    题目大意给定\(n\)张卡牌,每张卡牌有\(k\)个属性,第\(i\)张卡牌的第\(j\)个属性为\(c_{i,j}\)。一个由\(3\)张卡牌\(x,y,z\)组成的集合被称作好的,当且仅当对于\(1\leqi\leqk\)均有\(c_{x,i}=c_{y,i}=c_{z,i}\)或者\(c_{x,i},c_{y,i},c_{z,i}\)两两不相等。......
  • CF1793E Velepin and Marketing 题解
    题目大意有\(n\)个读者,第\(i\)年他们要一起读\(k_i\)本书,每一本书至少要让一个人读,每一个人也只能读恰好一本书。如果某一个人\(j\),如果有\(a_j\)个人这一年里和他读了同一本书,那么他就会感到满足。对于所有的\(q\)组询问,每组给定一个\(k_i\),求感到满足的人数的最......
  • CF1735E House Planning 题解
    题目大意一条直线上有\(n\)个房子和两个人,房子的坐标\(d_1,d_2,d_3\dotsd_n\),以及两个人坐标为\(p_1,p_2\)。现在会告诉你两个集合\(S_1=\{|p_1-d_i|,1\leqi\leqn\}\)以及\(S_2=\{|p_2-d_i|,1\leqi\leqn\}\)。这个写法可能不是很规范,但为了美观就写成这样了。......
  • ARC171 - sol
    感觉难度并不大,但是就是做不起,呜呜呜。中等题练少了是这样的。AtCoderRegularContest171-AtCoderA-NoAttackingThereisachessboardwith\(N\)rowsand\(N\)columns.Let\((i,j)\)denotethesquareatthe\(i\)-throwfromthetopandthe\(j\)-thco......