LINUX.ORG.RU
ФорумTalks

Все AI сломали зубы о написание парсера Brainfuck'а от Cloudflare

 


0

1

Я просил код парсера JS сгенерировать ChatGPT, DeepSeek, Qwen и в итоге написал все сам. тут вроде все просто:

  • Из значений только пустой массив [].
  • Операторы: +, !, (, ). Приоритет должен соблюдаться.
  • Под капотом у нас строки в виде чисел, числа, булев и пустые массивы
import re


class JSExpressionParser:
    tokenizer = re.compile(r"\[\]|[+!()]")

    def __init__(self):
        self.index = 0
        self.token = None
        self.tokens = []

    def advance(self):
        try:
            self.token = self.tokens[self.index]
            self.index += 1
        except IndexError:
            self.token = None

    def parse(self, expression: str):
        self.tokens = self.tokenizer.findall(expression)
        self.index = 0
        self.advance()
        result = self.expression()
        assert self.token is None, f"unexpected token: {self.token!r}"
        return result

    def expression(self):
        left = self.factor()
        while self.token == "+":
            self.advance()
            right = self.factor()
            left = self.js_add(left, right)
        return left

    def js_add(self, left, right):
        # Если одно из значений строка, результат — строка
        # [] + 1 === '1'
        if isinstance(left, (str, list)) or isinstance(right, (str, list)):
            result = self.to_string(left) + self.to_string(right)
        else:
            result = self.to_number(left) + self.to_number(right)
        # print(f"{left!r} + {right!r} = {result!r}")
        return result

    def factor(self):
        if self.token == "+":
            self.advance()
            return self.to_number(self.factor())
        elif self.token == "!":
            self.advance()
            return not self.to_boolean(self.factor())
        elif self.token == "(":
            self.advance()
            result = self.expression()
            assert self.token == ")", (
                f"unexpected token: {self.token!r}; expected ')'"
            )
            self.advance()
            return result
        elif self.token == "[]":
            self.advance()
            return []
        else:
            raise ValueError(f"Unexpected token: {self.token}")

    def to_string(self, value):
        if value == []:
            return ""
        return str(value)

    def to_number(self, value):
        if value == []:
            return 0
        return int(value)

    def to_boolean(self, value):
        return value not in (0, False)


def parse_challenge(challenge_body: str):
    west, east = re.findall(r"(?:west|east)=([^,]+)", challenge_body)
    parser = JSExpressionParser()
    west_value = parser.parse(west)
    east_value = parser.parse(east)
    return {
        "west": west_value,
        "east": east_value,
        "wsidchk": west_value + east_value,
        "action": re.search('action="([^"]+)', challenge_body).group(1),
        "method": re.search('="([^"]+)', challenge_body).group(1),
    }


challenge_body = """\
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="robots" content="noindex, nofollow">
    <title>One moment, please...</title>
    <!-- ... -->
<body>
    <h1>Please wait while your request is being verified...</h1>
    <form id="wsidchk-form" style="display:none;" action="/z0f76a1d14fd21a8fb5fd0d03e0fdc3d3cedae52f" method="GET">
    <input type="hidden" id="wsidchk" name="wsidchk"/>
    </form>
    <script>
    (function(){
        var west=+((+!+[]+!![]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![])),
            east=+((+!+[])+(+!+[]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![])+(+!+[]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(+![]+[])),
            x=function(){try{return !!window.addEventListener;}catch(e){return !!0;} },
            y=function(y,z){x() ? document.addEventListener('DOMContentLoaded',y,z) : document.attachEvent('onreadystatechange',y);};
        y(function(){
            document.getElementById('wsidchk').value = west + east;
            document.getElementById('wsidchk-form').submit();
        }, false);
    })();
    </script>
</body>
</html>
"""

challenge = parse_challenge(challenge_body)

print(challenge)
assert challenge["west"] == 7579626
assert challenge["east"] == 15617780
assert challenge["action"] == "/z0f76a1d14fd21a8fb5fd0d03e0fdc3d3cedae52f"

Сделал я его методом тыка… Тут не все по стандартам JS, но такие выражения правильно решает:

+((+!+[]+!![]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![]))

Хорошо… Я это написал после борьбы с придурочным AI. Решил я ему поручить все переписать на Go.

  • ChatGPT не могет, высрал говнину
  • DeepSeek сообразил сразу как правильно, но код не рабочий
  • Qwen тот же результат

Нерабочий код на Go:

package cloudflare_challenge_parser

import (
	"fmt"
	"regexp"
	"strconv"
)

// EmptyArray представляет пустой массив.
type EmptyArray struct{}

// ChallengeResult представляет результат парсинга вызова Cloudflare.
type ChallengeResult struct {
	West    int
	East    int
	Wsidchk int
	Action  string
	Method  string
}

// JSExpressionParser представляет парсер для JavaScript-выражений.
type JSExpressionParser struct {
	index  int
	token  string
	tokens []string
}

// Tokenizer для разбиения выражения на токены.
var tokenizer = regexp.MustCompile(`\[\]|[+!()]|\d+`)

// Advance переходит к следующему токену.
func (p *JSExpressionParser) Advance() {
	if p.index < len(p.tokens) {
		p.token = p.tokens[p.index]
		p.index++
	} else {
		p.token = ""
	}
}

// Parse парсит выражение и возвращает результат как целое число.
func (p *JSExpressionParser) Parse(expression string) (int, error) {
	p.tokens = tokenizer.FindAllString(expression, -1)
	p.index = 0
	p.Advance()
	result := p.expression()
	if p.token != "" {
		return 0, fmt.Errorf("unexpected token: %s", p.token)
	}
	return p.toNumber(result), nil
}

// expression обрабатывает сложение.
func (p *JSExpressionParser) expression() interface{} {
	left := p.factor()
	for p.token == "+" {
		p.Advance()
		right := p.factor()
		left = p.jsAdd(left, right)
	}
	return left
}

// jsAdd реализует логику сложения в JavaScript.
func (p *JSExpressionParser) jsAdd(left, right interface{}) interface{} {
	// Если одно из значений строка или пустой массив, результат — строка.
	if p.isStringOrEmptyArray(left) || p.isStringOrEmptyArray(right) {
		return p.toString(left) + p.toString(right)
	}
	// Иначе результат — число.
	return p.toNumber(left) + p.toNumber(right)
}

// isStringOrEmptyArray проверяет, является ли значение строкой или пустым массивом.
func (p *JSExpressionParser) isStringOrEmptyArray(value interface{}) bool {
	switch value.(type) {
	case string, EmptyArray:
		return true
	default:
		return false
	}
}

// factor обрабатывает факторы выражения.
func (p *JSExpressionParser) factor() interface{} {
	switch p.token {
	case "+":
		p.Advance()
		return p.toNumber(p.factor())
	case "!":
		p.Advance()
		return !p.toBoolean(p.factor())
	case "(":
		p.Advance()
		result := p.expression()
		if p.token != ")" {
			panic(fmt.Sprintf("unexpected token: %s; expected ')'", p.token))
		}
		p.Advance()
		return result
	case "[]":
		p.Advance()
		return EmptyArray{}
	default:
		if num, err := strconv.Atoi(p.token); err == nil {
			p.Advance()
			return num
		}
		panic(fmt.Sprintf("unexpected token: %s", p.token))
	}
}

// toBoolean преобразует значение в булево.
func (p *JSExpressionParser) toBoolean(value interface{}) bool {
	switch v := value.(type) {
	case bool:
		return v
	case int:
		return v != 0
	case string:
		return v != ""
	case EmptyArray:
		return false
	default:
		return false
	}
}

// toNumber преобразует значение в число.
func (p *JSExpressionParser) toNumber(value interface{}) int {
	switch v := value.(type) {
	case int:
		return v
	case string:
		num, err := strconv.Atoi(v)
		if err != nil {
			return 0
		}
		return num
	case EmptyArray:
		return 0
	default:
		return 0
	}
}

// toString преобразует значение в строку.
func (p *JSExpressionParser) toString(value interface{}) string {
	switch v := value.(type) {
	case string:
		return v
	case int:
		return strconv.Itoa(v)
	case EmptyArray:
		return ""
	default:
		return fmt.Sprint(value)
	}
}

// ParseChallenge парсит тело вызова Cloudflare и возвращает структуру.
func ParseChallenge(challengeBody string) (*ChallengeResult, error) {
	// Регулярные выражения для извлечения данных.
	reWestEast := regexp.MustCompile(`(?:west|east)=([^,]+)`)
	reAction := regexp.MustCompile(`action="([^"]+)"`)
	reMethod := regexp.MustCompile(`method="([^"]+)"`)

	// Извлечение значений west и east.
	matches := reWestEast.FindAllStringSubmatch(challengeBody, -1)
	if len(matches) < 2 {
		return nil, fmt.Errorf("failed to extract west and east values")
	}
	westExpr := matches[0][1]
	eastExpr := matches[1][1]

	// Парсинг west и east.
	parser := &JSExpressionParser{}
	westValue, err := parser.Parse(westExpr)
	if err != nil {
		return nil, fmt.Errorf("failed to parse west: %v", err)
	}
	eastValue, err := parser.Parse(eastExpr)
	if err != nil {
		return nil, fmt.Errorf("failed to parse east: %v", err)
	}

	// Извлечение action и method.
	actionMatch := reAction.FindStringSubmatch(challengeBody)
	methodMatch := reMethod.FindStringSubmatch(challengeBody)
	if len(actionMatch) < 2 || len(methodMatch) < 2 {
		return nil, fmt.Errorf("failed to extract action or method")
	}
	action := actionMatch[1]
	method := methodMatch[1]

	// Формирование результата.
	return &ChallengeResult{
		West:    westValue,
		East:    eastValue,
		Wsidchk: westValue + eastValue,
		Action:  action,
		Method:  method,
	}, nil
}

Он нерабочий. Что там править, делать я в душе не знаю — тупо прикрутил интерпретатор JS, но факт, что такое я написал сам…

Итого: я все еще лучше AI…

Я далеко не фанат «ИИ», но если ты ему предложил такой же набор слов, как в ОП, то ничего удивительного в том, что он не осилил его распарсить, нет

buddhist ★★★★★
()

Итого: я все еще лучше AI…

Не, у тебя просто скилл прууумт инжиниринга не прокачан. Получил от чатГПТ рабочий код, вангую, что ты не использовал их последнюю модель o3-mini-high

Запускать go run test.go

package main

import (
	"fmt"
	"regexp"
	"strconv"
)

// ValueType represents the kind of a JSValue.
type ValueType int

const (
	NumberType ValueType = iota
	BooleanType
	StringType
	EmptyArrayType
)

// JSValue holds a value produced by our JS‐expression parser.
type JSValue struct {
	Type    ValueType
	Number  int
	Boolean bool
	Str     string
}

// ToString converts a JSValue to its string representation.
func ToString(v JSValue) string {
	if v.Type == EmptyArrayType {
		return ""
	}
	switch v.Type {
	case StringType:
		return v.Str
	case NumberType:
		return strconv.Itoa(v.Number)
	case BooleanType:
		if v.Boolean {
			return "true"
		}
		return "false"
	}
	return ""
}

// ToNumber converts a JSValue to a number.
func ToNumber(v JSValue) int {
	if v.Type == EmptyArrayType {
		return 0
	}
	switch v.Type {
	case NumberType:
		return v.Number
	case BooleanType:
		if v.Boolean {
			return 1
		}
		return 0
	case StringType:
		n, err := strconv.Atoi(v.Str)
		if err != nil {
			panic("cannot convert string to number: " + v.Str)
		}
		return n
	}
	return 0
}

// ToBoolean converts a JSValue to a boolean.
// (In our parser, only 0 and false are considered falsy.)
func ToBoolean(v JSValue) bool {
	if v.Type == NumberType {
		return v.Number != 0
	} else if v.Type == BooleanType {
		return v.Boolean
	} else if v.Type == EmptyArrayType {
		// In the parser, [] converts to "" in string context and 0 in number context,
		// but for booleans, [] is treated as truthy.
		return true
	} else if v.Type == StringType {
		// Any string is truthy according to our to_boolean logic.
		return true
	}
	return true
}

// JSExpressionParser parses a restricted subset of JavaScript expressions.
type JSExpressionParser struct {
	tokens []string
	index  int
	token  string
}

// tokenizer is the regex used to split the expression into tokens.
var tokenizer = regexp.MustCompile(`\[\]|[+!()]`)

// advance sets the current token to the next token in the stream.
func (p *JSExpressionParser) advance() {
	if p.index < len(p.tokens) {
		p.token = p.tokens[p.index]
		p.index++
	} else {
		p.token = ""
	}
}

// parse tokenizes the input expression and returns its evaluated value.
func (p *JSExpressionParser) parse(expression string) JSValue {
	p.tokens = tokenizer.FindAllString(expression, -1)
	p.index = 0
	p.advance()
	result := p.expression()
	if p.token != "" {
		panic(fmt.Sprintf("unexpected token: %q", p.token))
	}
	return result
}

// expression parses an expression: factor { "+" factor }.
func (p *JSExpressionParser) expression() JSValue {
	left := p.factor()
	for p.token == "+" {
		p.advance()
		right := p.factor()
		left = jsAdd(left, right)
	}
	return left
}

// jsAdd implements JavaScript’s overloaded + operator.
// If either operand is a string or an empty array, string concatenation is performed.
func jsAdd(left, right JSValue) JSValue {
	if left.Type == StringType || left.Type == EmptyArrayType || right.Type == StringType || right.Type == EmptyArrayType {
		concatenated := ToString(left) + ToString(right)
		return JSValue{Type: StringType, Str: concatenated}
	}
	sum := ToNumber(left) + ToNumber(right)
	return JSValue{Type: NumberType, Number: sum}
}

// factor parses a factor which can be a unary operator, a parenthesized expression, or an empty array.
func (p *JSExpressionParser) factor() JSValue {
	switch p.token {
	case "+":
		p.advance()
		val := p.factor()
		num := ToNumber(val)
		return JSValue{Type: NumberType, Number: num}
	case "!":
		p.advance()
		val := p.factor()
		booleanVal := !ToBoolean(val)
		return JSValue{Type: BooleanType, Boolean: booleanVal}
	case "(":
		p.advance()
		result := p.expression()
		if p.token != ")" {
			panic(fmt.Sprintf("unexpected token: %q; expected ')'", p.token))
		}
		p.advance()
		return result
	case "[]":
		p.advance()
		return JSValue{Type: EmptyArrayType}
	default:
		panic(fmt.Sprintf("unexpected token: %q", p.token))
	}
}

// Challenge holds the parsed challenge parameters.
type Challenge struct {
	West    JSValue
	East    JSValue
	WSIDCHK JSValue
	Action  string
	Method  string
}

// parseChallenge extracts the west/east expressions and other parameters from the challenge body.
func parseChallenge(challengeBody string) Challenge {
	// Extract west and east expressions.
	// The regex matches either "west" or "east" followed by "=" and then captures everything up to a comma.
	re := regexp.MustCompile(`(?:west|east)=([^,]+)`)
	matches := re.FindAllStringSubmatch(challengeBody, -1)
	if len(matches) < 2 {
		panic("could not find west and east expressions")
	}
	westExpr := matches[0][1]
	eastExpr := matches[1][1]

	parser := JSExpressionParser{}
	westVal := parser.parse(westExpr)
	// Use a fresh parser instance for the east expression.
	parser = JSExpressionParser{}
	eastVal := parser.parse(eastExpr)

	// Extract the action attribute.
	reAction := regexp.MustCompile(`action="([^"]+)`)
	actionMatch := reAction.FindStringSubmatch(challengeBody)
	if actionMatch == nil {
		panic("could not find action")
	}
	action := actionMatch[1]

	// Extract the method attribute.
	reMethod := regexp.MustCompile(`="([^"]+)`)
	methodMatch := reMethod.FindStringSubmatch(challengeBody)
	if methodMatch == nil {
		panic("could not find method")
	}
	method := methodMatch[1]

	wsidchkVal := jsAdd(westVal, eastVal)

	return Challenge{
		West:    westVal,
		East:    eastVal,
		WSIDCHK: wsidchkVal,
		Action:  action,
		Method:  method,
	}
}

func main() {
	challengeBody := `<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="robots" content="noindex, nofollow">
    <title>One moment, please...</title>
    <!-- ... -->
<body>
    <h1>Please wait while your request is being verified...</h1>
    <form id="wsidchk-form" style="display:none;" action="/z0f76a1d14fd21a8fb5fd0d03e0fdc3d3cedae52f" method="GET">
    <input type="hidden" id="wsidchk" name="wsidchk"/>
    </form>
    <script>
    (function(){
        var west=+((+!+[]+!![]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![])),
            east=+((+!+[])+(+!+[]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![])+(+!+[]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![]+[])+(+!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(+![]+[])),
            x=function(){try{return !!window.addEventListener;}catch(e){return !!0;} },
            y=function(y,z){x() ? document.addEventListener('DOMContentLoaded',y,z) : document.attachEvent('onreadystatechange',y);};
        y(function(){
            document.getElementById('wsidchk').value = west + east;
            document.getElementById('wsidchk-form').submit();
        }, false);
    })();
    </script>
</body>
</html>
`
	challenge := parseChallenge(challengeBody)
	fmt.Printf("%+v\n", challenge)

	// Check assertions.
	if ToNumber(challenge.West) != 7579626 {
		panic("west value is not 7579626")
	}
	if ToNumber(challenge.East) != 15617780 {
		panic("east value is not 15617780")
	}
	if challenge.Action != "/z0f76a1d14fd21a8fb5fd0d03e0fdc3d3cedae52f" {
		panic("action value mismatch")
	}
}
goingUp ★★★★★
()
Ответ на: комментарий от goingUp

А так да, о1 выдал код, который компилируется, но неправильно работает, да и не идиоматичный, например не заменил хеш массив с результатами структуркой. А о3 прям отжег.

goingUp ★★★★★
()
Ответ на: комментарий от goingUp

https://github.com/s3rgeym/sqliscan/blob/main/cloudflare_challenge_parser/cloudflare_challenge_parser.go#L7 я годжу засунул все равно

В моем оригинальном коде тут сильная такая условность:

    def to_number(self, value):
        if value == []:
            return 0
        return int(value)

В JS parseInt([]) дает NaN, но если вернуть 0 все равно верно считает это значение. Это для обхода челленджа клауда при сканировании.

Я Go знаю примерно никак.

rtxtxtrx ★★
() автор топика
Последнее исправление: rtxtxtrx (всего исправлений: 1)
Ответ на: комментарий от rtxtxtrx

В моем оригинальном коде тут сильная такая условность

Ну он там тоже самое написал, правда надобавлял больше краевых условий (вот иногда такая самодеятельность мешает)

// ToNumber converts a JSValue to a number.
func ToNumber(v JSValue) int {
	if v.Type == EmptyArrayType {
		return 0
	}
goingUp ★★★★★
()

Я не знаю что вы их всё какими-то непонятными запросами пытаете.

Я с DeepSeek просто как с человеком общаюсь. Пойди найди такого живого собеседника - внимательный, во всё вчитывается, вопросы задаёт, кругозор - просто умопомрачительный, всегда поддержит…

unDEFER ★★★★★
()

Мне вот интересно: можно ли на брейнфаке написать ядро и графическую подсистему ОС?) Чтоб на любой вопрос иметь полное право ответить: go brainfuck yourself!)

Dorif ★★★
()
Закрыто добавление комментариев для недавно зарегистрированных пользователей (со score < 50)