某GPT网站问答流程逆向和Python复现

查看 213|回复 10
作者:jjjzw   
由于魔法释放不太稳定,我被chatGPT拒之门外,但是我已经不能离开GPT了(悲
于是找了个新的使用GPT模型训练的免费AI代替(ps:也
[color=]需要魔法
,优点是免登录,不被IP安全性限制)
在一次白嫖时,我灵光一现,想要分析下该网站问答的流程,顺便复习下长时间不用的python
逆向分析
F12打开开发者工具,输入问题,点击发送,查看Network记录

发现有两条请求,第一条是普通的XMLHttpRequest,第二条是一个stream流,联想到网站显示回答时通常一个字一个字逐步显示,回答应该就是通过stream流传输的

查看该流,果然将答案拆开传输,这就是我们的目标请求了
观察stream流请求:

这样的不带参数的get请求如何使服务端确定问题是什么呢,猜测网站对cookie进行了操作,查看两条请求的cookie:
1.submit的cookie

2.stream的cookie

发现在submit后,响应体设置了新的cookie:session,因此只要获取该session的cookie值,就可以编写请求索取GPT的回答。为了获取session值,则需要先发送submit请求,观察该请求,发现这是一个POST请求,通过Payload的方式上传数据

这里有两个参数:his和prompt,prompt很好理解,就是发送的问题,此处进行了加密处理,his是一个列表,大概率是历史记录(该GPT有根据上文回答问题的功能),那么我们先分析prompt的生成
观察到submit请求的Initiator是main.js,直接到该文件中搜索"prompt",发现并没有结果
观察main.js,发现是加密混淆过的,其中有大量的十六进制字符串和一堆毫无意义的偏移和函数

把文件送入解密网站,把代码进行解码,再次搜索"prompt"
成功定位到了位置:
var W = input_value;  // 输入的问题字符串
document[n(zZ.Xb, 0x145, zZ.js, zZ.XR) + 'Ele' + n(0x1de, 0x30e, 'z$N#', 0x1e2) + 'tBy' + 'Id'](I[d(zZ.Xm, zZ.XV, 0x25, -zZ.XM) + 'kc'])[d(zZ.XO, zZ.XH, 0x45, 0x154) + 'ue'] = '';
var y = I[d(zZ.Xt, zZ.XQ, -0x147, zZ.Xe) + 'SF'](P, W);
D[n(0x1b5, zZ.um, 'YS3C', 0x114) + 'd'](JSON[d('5l3F', -0xef, -zZ.XT, -0x20f) + n(zZ.Xl, 0x174, 'jHS6', -zZ.Xs) + n(0xea, zZ.XZ, zZ.Xa, -zZ.XG)]({
    'prompt': y,
    'his': window[n(zZ.XJ, -0x2, '2Qf[', 0x74) + d('[qgO', -zZ.XF, -zZ.Xr, -zZ.jz) + 'ys'][n(zZ.Xc, 0x154, 'i2$#', 0x1bc) + d(zZ.Xv, -zZ.XE, -zZ.Xh, -0x2ad)]
整个代码都被混淆了,但依稀还能见到一些影子,比如第二行中能拼凑出熟悉的ElementById
这里能发现,prompt值就是y,而his的值则是先比较了historyCheck的值,如果选择了则为列表值,未选择则为空列表[]
重点在y的计算上,此处打上断点,重新发送,定位到该函数的位置:
'\x5a\x41\x7a\x53\x46': function(x, U) {
    return x(U);
},
即:
var y = P(W);
跳转到函数P:
function P(x) {
    var zf = {
        I: 0x23,
        P: 0x159
    };
    const U = CryptoJS[K(zw.I, zw.P, zw.D, 0x634)][K(zw.W, zw.y, zw.x, 0x5f7) + '8'][L(-0x172, -0x1a6, zw.U, -0x1cd) + 'se'](I[K(0x4f8, zw.p, zw.f, 0x348) + 'DP']);
    function K(I, P, D, W) {
        return d(P, I - zp.I, D - zp.P, W - zp.D);
    }
    const p = CryptoJS[L(zw.w, -zw.C, 'Oh0s', -0x88)][K(0x57a, '8Xtv', 0x689, 0x522) + '8'][L(-0x3a, -0xf1, 'TyCN', zw.k) + 'se'](I[L(-0xec, 0x78, zw.N, -zw.B) + 'gQ']);
    let f = CryptoJS[L(-0x29a, -zw.q, '!mzD', -zw.Y)][K(zw.A, zw.zC, zw.zk, zw.zN) + 'ryp' + 't'](x, U, {
        'iv': p,
        'mode': CryptoJS[K(zw.zB, zw.zq, zw.zY, 0x208) + 'e'][K(zw.zA, 'ui19', zw.zb, zw.zR)],
        'padding': CryptoJS[K(zw.zm, zw.zV, 0x600, 0x58c)][L(-zw.zM, -zw.zO, zw.zH, -0x11) + K(0x4b5, zw.zt, 0x65a, zw.zQ) + L(zw.ze, -zw.zT, zw.zl, -zw.zs) + 'ng']
    })['toS' + L(zw.zZ, 0x52, 'AO11', -zw.za) + 'ng']();
    console[K(zw.zG, zw.zJ, 0x3ed, 0x48f)](f);
    function L(I, P, D, W) {
        return d(D, W - -zf.I, D - 0x2b, W - zf.P);
    }
    return f;
}
看到CryptoJS关键字就知道,原来是老朋友AES加密,加密代码一般长这样:
let f = CryptoJS.AES.encrypt(data, key, {
    'iv': iv,
    'mode': CryptoJS.mode.CBC,
    'padding': CryptoJS.pad.ZeroPadding
}).toString();
因此,只需要知道明文data,偏移量iv,密钥key,加密模式mode和padding模式就能算出密文
将混淆的代码函数片段拖入console中运行,逐步得到关键信息,将代码简化后可以得到:
function P(x) {
    const U = CryptoJS.enc.Utf8.parse('y~.,ZaabH6Ri?L7*');
    const p = CryptoJS.enc.Utf8.parse('koJ)KZhR1RW)!_~M');
    let f = CryptoJS.AES.encrypt(x, U, {
        'iv': p,
        'mode': CryptoJS.mode.CBC,
        'padding': CryptoJS.pad.ZeroPadding
    }).toString();
    return f;
}
这样就一目了然了,以此即可复现整个问答流程
脚本复现
有了加密方式,便尝试使用python复现该过程,
所需库:
requests
BeautifulSoup4
pycryptodome
之前写过一个密码管理工具,恰好用了相同的加密技术(
https://www.52pojie.cn/thread-1674964-1-1.html
把加密函数直接拿来用
完整代码:
# coding=gbk
from Crypto.Cipher import AES
from bs4 import BeautifulSoup as bs
import base64
import requests
import json
import re
def prompt(text):  # 加密函数
    key = "y~.,ZaabH6Ri?L7*".zfill(16).encode("utf-8")
    iv = "koJ)KZhR1RW)!_~M".zfill(16).encode("utf-8")
    cipher = AES.new(key, AES.MODE_CBC, iv)
    temp = b""
    data = text.encode()
    data = data + "\0".encode() * (16 - len(data) % 16)
    for i in range(int(len(data) / 16)):
        block = data[16 * i: 16 * (i + 1)]
        b = cipher.encrypt(block)
        temp += b
    res = base64.b64encode(temp).decode("utf-8")
    return res
def submit(pro):
    his = []
    url = "https://gpt.tool00.com/api/v1/chat/submit"
    headers = {
        "accept": "*/*",
        "accept-encoding": "gzip, deflate, br",
        "accept-language": "zh-CN,zh;q=0.9",
        "content-type": "application/json",
        "cookie": "_ga=GA1.1.800331461.1681885375; FCNEC=%5B%5B%22AKsRol9hmarvKjjCFyL8FNDRa4pULxPROMocI7VYSD0RbFdjyJTZC7l-bg8X9jiqb_sgT20JjKZUcByZcxCISeOtpn6yrS-MfP7fBKq_bnxmfgqatIjGyGj94fs7mzCuRdhlqjb3wptJRkxJW7PtMVyBrDHMULZplA%3D%3D%22%5D%2Cnull%2C%5B%5D%5D; __gads=ID=687486c546a2283f-22c4ae5397e0000b:T=1683182853:RT=1683182853:S=ALNI_MZCmtWOxQGyc3bhufEj8nnhyv6Pqg; __gpi=UID=00000c01738ff6e2:T=1683182853:RT=1683182853:S=ALNI_MZfy93-Hs1rY1R4PPrdQzg6CqPS3w; _ga_60RX9FESNC=GS1.1.1683187497.3.0.1683187497.0.0.0",
        "origin": "https://gpt.tool00.com",
        "referer": "https://gpt.tool00.com/",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
    }
    data = {
        "prompt": pro,
        "his": his
    }
    r = requests.post(url, headers=headers, data=json.dumps(data))
    session = re.search("[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}", r.headers["set-cookie"])
    return session.group()
def stream(session):
    url = "https://gpt.tool00.com/api/v1/chat/stream"
    headers = {
        "accept": "*/*",
        "accept-encoding": "gzip, deflate, br",
        "accept-language": "zh-CN,zh;q=0.9",
        "cache-control": "no-cache",
        "content-type": "application/json",
        "cookie": "_ga=GA1.1.800331461.1681885375; FCNEC=%5B%5B%22AKsRol9hmarvKjjCFyL8FNDRa4pULxPROMocI7VYSD0RbFdjyJTZC7l-bg8X9jiqb_sgT20JjKZUcByZcxCISeOtpn6yrS-MfP7fBKq_bnxmfgqatIjGyGj94fs7mzCuRdhlqjb3wptJRkxJW7PtMVyBrDHMULZplA%3D%3D%22%5D%2Cnull%2C%5B%5D%5D; __gads=ID=687486c546a2283f-22c4ae5397e0000b:T=1683182853:RT=1683182853:S=ALNI_MZCmtWOxQGyc3bhufEj8nnhyv6Pqg; __gpi=UID=00000c01738ff6e2:T=1683182853:RT=1683182853:S=ALNI_MZfy93-Hs1rY1R4PPrdQzg6CqPS3w; _ga_60RX9FESNC=GS1.1.1683187497.3.0.1683187497.0.0.0; session=" + session,
        "origin": "https://gpt.tool00.com",
        "referer": "https://gpt.tool00.com/",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
    }
    r = requests.get(url, headers=headers)
    b = bs(r.content, "lxml")
    extr = re.findall('"content": ".*"', str(b))
    ans = ""
    for i, n in enumerate(extr):
        if i >= 1:
            ans += n[12:-1]
    print(">> " + ans.encode("utf-8").decode('unicode_escape'))
text = '你知道"吾爱破解"网站吗?'  # 这里输入问题
print(f">> {text}")
stream(submit(prompt(text)))
运行结果:
>> 你知道"吾爱破解"网站吗?
>> 我是一个AI语言模型,我知道“吾爱破解”网站。它是一个技术交流社区,主要讨论软件破解、逆向工程、网络安全等方面的内容。该网站也提供了一些破解软件和工具的下载。但需要注意的是,破解软件和工具可能存在安全风险,使用需谨慎。
(若要使用his功能,只需把之前的提问字符串append到his列表中一起提交即可)
————5.6补充his————
代码改为:
# coding=gbk
from Crypto.Cipher import AES
import base64
import requests
import json
from bs4 import BeautifulSoup as bs
import re
def prompt(text):
    key = "y~.,ZaabH6Ri?L7*".zfill(16).encode("utf-8")
    iv = "koJ)KZhR1RW)!_~M".zfill(16).encode("utf-8")
    cipher = AES.new(key, AES.MODE_CBC, iv)
    temp = b""
    data = text.encode()
    data = data + "\0".encode() * (16 - len(data) % 16)
    for i in range(int(len(data) / 16)):
        block = data[16 * i: 16 * (i + 1)]
        b = cipher.encrypt(block)
        temp += b
    res = base64.b64encode(temp).decode("utf-8")
    return res
def submit(pro, his):
    url = "https://gpt.tool00.com/api/v1/chat/submit"
    headers = {
        "accept": "*/*",
        "accept-encoding": "gzip, deflate, br",
        "accept-language": "zh-CN,zh;q=0.9",
        "content-type": "application/json",
        "cookie": "_ga=GA1.1.800331461.1681885375; FCNEC=%5B%5B%22AKsRol9hmarvKjjCFyL8FNDRa4pULxPROMocI7VYSD0RbFdjyJTZC7l-bg8X9jiqb_sgT20JjKZUcByZcxCISeOtpn6yrS-MfP7fBKq_bnxmfgqatIjGyGj94fs7mzCuRdhlqjb3wptJRkxJW7PtMVyBrDHMULZplA%3D%3D%22%5D%2Cnull%2C%5B%5D%5D; __gads=ID=687486c546a2283f-22c4ae5397e0000b:T=1683182853:RT=1683182853:S=ALNI_MZCmtWOxQGyc3bhufEj8nnhyv6Pqg; __gpi=UID=00000c01738ff6e2:T=1683182853:RT=1683182853:S=ALNI_MZfy93-Hs1rY1R4PPrdQzg6CqPS3w; _ga_60RX9FESNC=GS1.1.1683187497.3.0.1683187497.0.0.0",
        "origin": "https://gpt.tool00.com",
        "referer": "https://gpt.tool00.com/",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
    }
    data = {
        "prompt": pro,
        "his": his
    }
    r = requests.post(url, headers=headers, data=json.dumps(data))
    session = re.search("[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}", r.headers["set-cookie"])
    return session.group()
def stream(session):
    url = "https://gpt.tool00.com/api/v1/chat/stream"
    headers = {
        "accept": "*/*",
        "accept-encoding": "gzip, deflate, br",
        "accept-language": "zh-CN,zh;q=0.9",
        "cache-control": "no-cache",
        "content-type": "application/json",
        "cookie": "_ga=GA1.1.800331461.1681885375; FCNEC=%5B%5B%22AKsRol9hmarvKjjCFyL8FNDRa4pULxPROMocI7VYSD0RbFdjyJTZC7l-bg8X9jiqb_sgT20JjKZUcByZcxCISeOtpn6yrS-MfP7fBKq_bnxmfgqatIjGyGj94fs7mzCuRdhlqjb3wptJRkxJW7PtMVyBrDHMULZplA%3D%3D%22%5D%2Cnull%2C%5B%5D%5D; __gads=ID=687486c546a2283f-22c4ae5397e0000b:T=1683182853:RT=1683182853:S=ALNI_MZCmtWOxQGyc3bhufEj8nnhyv6Pqg; __gpi=UID=00000c01738ff6e2:T=1683182853:RT=1683182853:S=ALNI_MZfy93-Hs1rY1R4PPrdQzg6CqPS3w; _ga_60RX9FESNC=GS1.1.1683187497.3.0.1683187497.0.0.0; session=" + session,
        "origin": "https://gpt.tool00.com",
        "referer": "https://gpt.tool00.com/",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
    }
    r = requests.get(url, headers=headers)
    b = bs(r.content, "lxml")
    extr = re.findall('"content": ".*"', str(b))
    ans = ""
    for i, n in enumerate(extr):
        if i >= 1:
            ans += n[12:-1]
    print(">> " + ans.encode("utf-8").decode('unicode_escape'))
history = []
while (text := input(">> ")):
    if text != "exit":
        stream(submit(prompt(text), history))
        history.append(text)
    else:
        break
结果如下:
>> 记住我说的话:我是Alice
>> 好的,我记住了,你是Alice。
>> 我的名字是什么,我刚刚告诉你了
>> 您刚刚告诉我您的名字是Alice。
>> exit

网站, 函数

jjjzw
OP
  


wjl0 发表于 2023-5-6 11:57
非常感谢楼主!如果最后一段改了以后submit函数是不是也需要改一下?

已经改了,我光记着上传最后一段了
lqm2008   

以后可以省下许多时间
quad366   

现在bing也能ai了,楼主可以试试
zebenben   


quad366 发表于 2023-5-5 14:54
现在bing也能ai了,楼主可以试试

bing 功能不全吧
sylainy   


zebenben 发表于 2023-5-5 14:56
bing 功能不全吧

bing也要魔法
deli52   

Bing已经开放了
jjjzw
OP
  


quad366 发表于 2023-5-5 14:54
现在bing也能ai了,楼主可以试试

bing也在用但是感觉速度有点慢
N80U   

这个有点复杂。。我只会用Bing
s1711880582   

好了 我已经看到了 可以删除了
您需要登录后才可以回帖 登录 | 立即注册

返回顶部