noahcoolboy / funcaptcha

A library used to interact with funcaptchas.
Other
186 stars 43 forks source link

Add support for v2, game type 4, and fix detection #37

Closed AlexandraBaker closed 1 year ago

AlexandraBaker commented 1 year ago

Everything should work based on my limited testing and websites were accepting the tokens that were generated. Anyone else who wants to test is welcome to!

BadAimWeeb commented 1 year ago

Fingerprints should match closely of what current browser will output. You can check my forked version for reference. (how I randomized canvasFp is inspired by how Tor Browser block canvas fingerprinting).

Apart from that, support for game type 4 ~works great~ has some issues when token has more than 1 wave. Correct answer only sometimes got recognized. Not sure what happened, will look at it later on.

sv-du commented 1 year ago

I assume this fixes the JSON.parse() error when calling getChallenge

AlexandraBaker commented 1 year ago

Fingerprints should match closely of what current browser will output. You can check my forked version for reference. (how I randomized canvasFp is inspired by how Tor Browser block canvas fingerprinting).

Apart from that, support for game type 4 ~works great~ has some issues when token has more than 1 wave. Correct answer only sometimes got recognized. Not sure what happened, will look at it later on.

The answer body needs render_type: "canvas", to be added. However, even though solved is true, there is still something being detected after that I have not quite figured out yet. Let me know if you make any progress.

BadAimWeeb commented 1 year ago

Fingerprints should match closely of what current browser will output. You can check my forked version for reference. (how I randomized canvasFp is inspired by how Tor Browser block canvas fingerprinting). Apart from that, support for game type 4 ~works great~ has some issues when token has more than 1 wave. Correct answer only sometimes got recognized. Not sure what happened, will look at it later on.

The answer body needs render_type: "canvas", to be added. However, even though solved is true, there is still something being detected after that I have not quite figured out yet. Let me know if you make any progress.

This might do the trick. Not tested yet.

AlexandraBaker commented 1 year ago

@BadAimWeeb I have fixed the issues, could you please try again?

romalescarl17 commented 1 year ago

Got this error. Any way to fix this?

SyntaxError: Unexpected end of JSON input at JSON.parse () at Session. (C:\Users\Carll\Desktop\PROJECT BOTBASED MGUI\node_modules\funcaptcha\lib\session.js:56:29) at Generator.next () at fulfilled (C:\Users\Carll\Desktop\PROJECT BOTBASED MGUI\node_modules\funcaptcha\lib\session.js:5:58) at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

BadAimWeeb commented 1 year ago

Got this error. Any way to fix this?

SyntaxError: Unexpected end of JSON input at JSON.parse () at Session. (C:\Users\Carll\Desktop\PROJECT BOTBASED MGUI\node_modules\funcaptcha\lib\session.js:56:29) at Generator.next () at fulfilled (C:\Users\Carll\Desktop\PROJECT BOTBASED MGUI\node_modules\funcaptcha\lib\session.js:5:58) at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

You are using stable (but broken) version, and should try out either this currrent PR:

npm install git+https://github.com/AlexandraBaker/funcaptcha.git#patch-8

or my fork (which is also based on this PR but patching even more things):

npm install git+https://github.com/BadAimWeeb/funcaptcha.git

Patches from this PR will (and should) be applied to stable soon (after everything is done).

AlexandraBaker commented 1 year ago

@Oli2004 Are you using the fork to solve the captcha? Does your code work if you generate and solve a captcha using a different method? (ex. using the SDK on the Roblox website) Are you using a proxy with the captcha? (regions must match for Roblox to accept the token) Is the User-Agent header the same? Could you send a snippet of your code? What region are you in? Does manually solving the captcha work? What type of challenge are you getting? (type 3/4, what are the instructions?)

Rupulsttiky27 commented 1 year ago

I have a mistake, im using challenge type 4 and when using getEmbedUrl return the embedurl but when I put it in an iframe it does not load anything, I think it must be loaded with javascript, any solution?

BadAimWeeb commented 1 year ago

@Oli2004 Are you using the fork to solve the captcha? Does your code work if you generate and solve a captcha using a different method? (ex. using the SDK on the Roblox website) Are you using a proxy with the captcha? (regions must match for Roblox to accept the token) Is the User-Agent header the same? Could you send a snippet of your code? What region are you in? Does manually solving the captcha work? What type of challenge are you getting? (type 3/4, what are the instructions?)

Roblox is updating something that disallow us to just request to /v2/login. If IP region didn't match, it would return "Token Validation Failed" instead. I guess token is fine but there is something else (tracking or something), will try using a browser to login though.

(using Tor because it's more reliable to generate token somehow, maybe it treats Tor as anonymous and just return challenge, not trying to assert IP risk)

AlexandraBaker commented 1 year ago

@BadAimWeeb Token Validation Failed has to do with the csrf token, not the captcha token. If you do not use the same region for solving the captcha as in your requests to Roblox or change the IP between requests to Roblox, it will not work.

sv-du commented 1 year ago

I'm getting type 4 captchas to work but Roblox isn't accepting them

AlexandraBaker commented 1 year ago

@zachariapopcorn Make sure to update your user agent

sv-du commented 1 year ago

@AlexandraBaker I have this for my UA Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0 Should I use the default one here?

AlexandraBaker commented 1 year ago

@zachariapopcorn Try Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36

sv-du commented 1 year ago

Still failed

// General captcha solving logic
let session = new funcaptcha.Session(captchaData, {
  userAgent: UA,
});
let challenge = await session.getChallenge();
let res: SolvedCaptchaResult;
if (challenge instanceof Challenge1) {
  BetterConsole.log(`Captcha type given: 1`);
  res = await solveChallenge1(interaction, client, challenge);
} else if (challenge instanceof Challenge3) {
  BetterConsole.log(`Captcha type given: 3`);
  res = await solveChallenge3(interaction, client, challenge);
} else if (challenge instanceof Challenge4) {
  BetterConsole.log(`Captcha type given: 4`);
  res = await solveChallenge4(interaction, client, challenge);
}
if (res.success) {
  captchaToken = captchaData.token;
} else {
  if (res.error !== "CE" && res.error !== "CF") {
    throw new Error(res.error);
  }
  return;
}

solveChallenge4

import Discord from 'discord.js';

import { Challenge4 } from 'funcaptcha/lib/challenge';

import fs from "fs";

import BotClient from '../../utils/classes/BotClient';
import BetterConsole from '../classes/BetterConsole';

import SolvedCaptchaResult from '../interfaces/SolvedCaptchaResult';

const map = {
    "0️⃣": 0,
    "1️⃣": 1,
    "2️⃣": 2,
    "3️⃣": 3,
    "4️⃣": 4,
    "5️⃣": 5
}

const keys = Object.keys(map);

export default async function solveChallenge4(interaction: Discord.CommandInteraction, client: BotClient, challenge: Challenge4): Promise<SolvedCaptchaResult> {
    try {
        let amountOfWaves = challenge.data.game_data.waves;
        await fs.promises.writeFile(`${process.cwd()}/Image.gif`, await challenge.getImage());
        let embed = client.embedMaker({title: "Captcha Required", description: `Logins require a captcha to be completed, please complete the captcha below\n\nObjective: ${challenge.instruction}\n\nGuide: https://i.imgur.com/05OYegq.png\n\nAmount of Waves: ${amountOfWaves}`, type: "info", author: interaction.user});
        await interaction.editReply({embeds: [embed]});
        for(let i = 0; i < amountOfWaves; i++) {
            await fs.promises.writeFile(`${process.cwd()}/Image.gif`, await challenge.getImage());
            let msg = await (interaction.channel as Discord.TextChannel).send({files: [`${process.cwd()}/Image.gif`]});
            for(let i = 0; i < keys.length; i++) {
                await msg.react(keys[i]);
            }
            let collected = await msg.awaitReactions({
                filter: (reaction: Discord.MessageReaction, user: Discord.User) => {
                    if(interaction.user.id !== user.id) return false;
                    if(keys.findIndex(key => key === reaction.emoji.name) === -1) return false;
                    return true;
                },
                time: client.config.collectorTime,
                max: 1
            });
            await msg.delete();
            if(collected.size === 0) {
                let embed = client.embedMaker({title: "Captcha Expired", description: "You didn't answer the captcha in time, please rerun the command", type: "error", author: interaction.user});
                await interaction.editReply({embeds: [embed]});
                return {success: false, error: "CE"};
            }
            let answer = map[collected.at(0).emoji.name];
            let answerResponse = await challenge.answer(answer);
            if(answerResponse.response === "answered" && answerResponse.solved === false) {
                let embed = client.embedMaker({title: "Captcha Failed", description: "You've failed the captcha, please rerun the command", type: "error", author: interaction.user});
                await interaction.editReply({embeds: [embed]});
                return {success: false, error: "CF"};
            }
        }
        return {success: true}; // answerResponse.response = "answered" and answerResponse.solved = true
    } catch(e) {
        BetterConsole.log(e);
        return {success: false, error: e};
    }
}
// Login logic
let embed = client.embedMaker({
  title: "Captcha Completed",
  description:
    "You've successfully completed the captcha, I am now attempting to login to the Roblox account",
  type: "info",
  author: interaction.user,
});
await interaction.editReply({ embeds: [embed] });
await fs.promises.unlink(`${process.cwd()}/Image.gif`);
res = await login(
  client,
  client.config.ROBLOX_USERNAME,
  client.config.ROBLOX_PASSWORD,
  csrfToken,
  rblxChallengeId,
  rblxChallengeMetadata.unifiedCaptchaId,
  captchaToken,
  rblxChallengeType
);
let rawCookie = res.headers.get("set-cookie");
if (!rawCookie) {
  let embed = client.embedMaker({
    title: "Error",
    description: `There was an error while trying to login to the Roblox account: ${
      (await res.json()).errors[0].message
    }`,
    type: "error",
    author: interaction.user,
  });
  return await interaction.editReply({ embeds: [embed] });
}

login

async function login(client: BotClient, username: string, password: string, csrfToken?: string, challengeId?: string, unifiedCaptchaId?: string, captchaToken?: string, challengeType?: string) {
    let headers = {
        "Content-Type": "application/json",
        "User-Agent": UA,
    }
    if(csrfToken) {
        headers["X-CSRF-TOKEN"] = csrfToken;
    }
    if(challengeId) {
        headers["rblx-challenge-id"] = challengeId;
        let metaData = JSON.stringify({
            "unifiedCaptchaId": unifiedCaptchaId,
            "captchaToken": captchaToken,
            "actionType": "Login"
        });
        headers["rblx-challenge-metadata"] = Buffer.from(metaData, "utf-8").toString("base64");
        headers["rblx-challenge-type"] = challengeType;
    }
    return await client.request({
        url: "https://auth.roblox.com/v2/login",
        method: "POST",
        headers: headers,
        body: {
            "ctype": "Username",
            "cvalue": username,
            "password": password
        },
        robloxRequest: false
    });
}
sv-du commented 1 year ago

Also I just realized that they updated their login logic to include this shit https://i.imgur.com/mDnD3Vr.png

Edit: These things don't seem to be required

sv-du commented 1 year ago

@AlexandraBaker ^

AlexandraBaker commented 1 year ago

@zachariapopcorn I do not have the time to debug your code/specific issue. Please refer to this working example where the answers are between 0 and 5:

const username = "testing12345"
const password = "testpassword"

const fs = require("fs")
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"

const undici = require("undici")
const funcaptcha = require("../lib")
const readline = require("readline")
let rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
})

function ask(question) {
    return new Promise((resolve, reject) => {
        rl.question(question, (answer) => {
            resolve(answer)
        })
    })
}

let csrf = "";

async function sendRequest(challengeId, fieldData, token) {
    const headers = {
        "x-csrf-token": csrf,
        "content-type": "application/json",
        "user-agent": USER_AGENT,
        'rblx-challenge-type': 'captcha',
        origin: "https://www.roblox.com",
        referer: "https://www.roblox.com/"
    }

    if (fieldData) {
        headers['rblx-challenge-metadata'] = Buffer.from(JSON.stringify({
            unifiedCaptchaId: fieldData.unifiedCaptchaId,
            captchaToken: token.token,
            actionType: fieldData.actionType,
        })).toString('base64')
    }

    if (challengeId) {
        headers['rblx-challenge-id'] = challengeId
    }

    const res3 = await undici.request("https://auth.roblox.com/v2/login", {
        method: "POST",
        headers,
        body: JSON.stringify({
            "ctype": "Username",
            "cvalue": username,
            "password": password,
        })
    })

    const newCsrf = res3.headers['x-csrf-token']

    if (newCsrf) {
        csrf = newCsrf
        return sendRequest(challengeId, fieldData, token)
    }

    const body = await res3.body.json()
    console.log('BODY', body)
    return { body, headers: res3.headers }
}

sendRequest().then(async ({ headers }) => {
    setTimeout(async () => {
        const fieldData = JSON.parse(Buffer.from(headers["rblx-challenge-metadata"], "base64"))

        const token = await funcaptcha.getToken({
            pkey: "476068BF-9607-4799-B53D-966BE98E2B81",
            surl: "https://client-api.arkoselabs.com",
            data: {
                "blob": fieldData.dataExchangeBlob,
            },
            headers: {
                "User-Agent": USER_AGENT,
            },
            site: "https://www.roblox.com",
            location: "https://www.roblox.com/login",
        })

        if (!token.token.includes('sup=1')) {
            const session = new funcaptcha.Session(token, {
                userAgent: USER_AGENT,
            })

            const challenge = await session.getChallenge().catch((err) => console.log('login fail', err))
            console.log(challenge.data.game_data.customGUI.api_breaker)
            console.log(challenge.instruction)

            for(let x = 0; x < challenge.data.game_data.waves; x++) {
                fs.writeFileSync(`image.gif`, await challenge.getImage())
                console.log(await challenge.answer(parseInt(await ask("Answer: "))))
            }
        } else {
            console.log('Suppressed captcha!')
        }

        sendRequest(headers['rblx-challenge-id'], fieldData, token).then(console.log)
    }, 2500)
})
sv-du commented 1 year ago

@AlexandraBaker

Huh, seems like I was missing the origin and referer headers and had the wrong surl

Thanks, though in testing, I sometimes had the following error TypeError: Cannot read properties of undefined (reading 'gameType') which came from the session file, but this is probably some random error that fixes itself

AlexandraBaker commented 1 year ago

@zachariapopcorn That probably means you got a suppressed captcha

sv-du commented 1 year ago

Thanks

thurdev commented 1 year ago

I have a mistake, im using challenge type 4 and when using getEmbedUrl return the embedurl but when I put it in an iframe it does not load anything, I think it must be loaded with javascript, any solution?

up +1

AlexandraBaker commented 1 year ago

@thurdev @Rupulsttiky27 This library does not support embedding the URL in an iframe, there are additional assets loaded to make the captcha work in an iframe. If you wish to solve captchas without detection, you will need to use the built in challenge class as shown in the README example.

AlexandraBaker commented 1 year ago

@Oli2004 This should be fixed now.

AlexandraBaker commented 1 year ago

@Oli2004 Are you sure that your answers are correct? What are the API breakers when they are failing?

Edit: Can you try logging in a bunch manually using the official captcha? I'm doing this and randomly I will go into a streak of failing even though the answers are correct.

BadAimWeeb commented 1 year ago

Game type 4 can now have from 5 to 16 (!) images to select. You might want to remove the 0-5 input limit. image

Edit: Can you try logging in a bunch manually using the official captcha? I'm doing this and randomly I will go into a streak of failing even though the answers are correct.

Also FunCaptcha might have hidden challenges for bad fingerprint (that's why you're failing). In my observation, I can only pass captcha after second tries (official client also does this).

AlexandraBaker commented 1 year ago

@BadAimWeeb Seeing as I am getting suppressed and 1 wave easy captchas I highly doubt it's due to a bad fingerprint. Since it's happening on the official client, I am inclined to believe this is an unavoidable mechanism that might be some sort of random failure. Arkose does have something called "punishable_actioned" which is described as "an attack mitigation tactic, which randomly fails verification attempts, even if the response was correct. This field indicates if punishable was activated."

AlexandraBaker commented 1 year ago

@Oli2004 Check if sup=1 is present in the token. This means the captcha was suppressed, aka an "invisible captcha," and the token can be used without generating a challenge.

AlexandraBaker commented 1 year ago

@Oli2004 It's probably because tbio and kbio are not included in the request. If you can send me the website, I can get some data when I have the time and add it in.

AlexandraBaker commented 1 year ago

@Oli2004 I think this is because Challenge3 was not updated the same way as Challenge4. I will work on a more standardized way to update all challenges.

ghost commented 1 year ago

ROBLOX Login API seems to only accept suppressed captchas and doesn't seem to accept non-suppressed ones.

Error: Challenge failed to authorize

I've already added origin and referer to my headers and it still doesn't work. Any help?

sv-du commented 1 year ago

I'm getting reports of people getting Error: Invalid API breaker once in a while. Do you know what this means, and if it's one of those errors that just fix itself if you try again?

sv-du commented 1 year ago

@AlexandraBaker This error has now become very consistent, making captcha attempts fail. Can you please fix when you can? Thanks

AlexandraBaker commented 1 year ago

@zachariapopcorn It has already been fixed, you need to update the package.

sv-du commented 1 year ago

I did, same error

AlexandraBaker commented 1 year ago

@zachariapopcorn The latest version does not have this error in the code.

noahcoolboy commented 1 year ago

Hi all, Support for gametype 4 and API Breakers V2 is currently being developed on a separate branch. I will be keeping this pull request open for any bugs found or feedback given. https://github.com/noahcoolboy/funcaptcha/tree/gt4 You can install it using the following command: npm i https://github.com/noahcoolboy/funcaptcha/tree/gt4

I will not provide help if you are using the library incorrectly. Thank you.

brandonapt commented 1 year ago

there seems to be an issue with getting the embed url (on both forks and the official branch). the gt4 branch just shows a white screen when embedded in a iframe, while @BadAimWeeb's fork creates this. it IS a valid session, as i can use getChallenge() and other functions fine. image

BadAimWeeb commented 1 year ago

there seems to be an issue with getting the embed url (on both forks and the official branch). the gt4 branch just shows a white screen when embedded in a iframe, while @BadAimWeeb's fork creates this. it IS a valid session, as i can use getChallenge() and other functions fine. image

You can't just only using embed URL in and expect that it will work. You also need to write some scripts to communicate to the iframe (which sadly, I don't have any PoC since code from funcaptcha is very obfuscated and I cannot figure out how yet).

noahcoolboy commented 1 year ago

there seems to be an issue with getting the embed url (on both forks and the official branch). the gt4 branch just shows a white screen when embedded in a iframe, while @BadAimWeeb's fork creates this. it IS a valid session, as i can use getChallenge() and other functions fine. image

getEmbedUrl seems to work just fine on the gt4 branch. See image below: image

Please make sure all parameters are configured correctly (site, blob, etc...). It seemed to have worked just fine (for me) when modifying test/test.js or logging the embedUrl and placing it in an iframe.

brandonapt commented 1 year ago

there seems to be an issue with getting the embed url (on both forks and the official branch). the gt4 branch just shows a white screen when embedded in a iframe, while @BadAimWeeb's fork creates this. it IS a valid session, as i can use getChallenge() and other functions fine. image

getEmbedUrl seems to work just fine on the gt4 branch. See image below:

image

Please make sure all parameters are configured correctly (site, blob, etc...). It seemed to have worked just fine (for me) when modifying test/test.js or logging the embedUrl and placing it in an iframe.

ah, thanks. i guess i’ll give the gt4 branch another try!

noahcoolboy commented 1 year ago

Merged in #39

seteri commented 1 year ago

there seems to be an issue with getting the embed url (on both forks and the official branch). the gt4 branch just shows a white screen when embedded in a iframe, while @BadAimWeeb's fork creates this. it IS a valid session, as i can use getChallenge() and other functions fine. image

getEmbedUrl seems to work just fine on the gt4 branch. See image below: image Please make sure all parameters are configured correctly (site, blob, etc...). It seemed to have worked just fine (for me) when modifying test/test.js or logging the embedUrl and placing it in an iframe.

ah, thanks. i guess i’ll give the gt4 branch another try!

did u find a solution for it? I have been trying to make embed work but nothing ...