Closed qazzaq666 closed 1 month ago
Было бы неплохо, но думаю апи для мини игр нет:)
There are a base64 ciphered number passes for claim reward. One part is accountId, and other part i dont know what. Guess it somehow generateing at front-end, but i actualy cant understand where and how. It is only one problem.
There are a base64 ciphered number passes for claim reward. One part is accountId, and other part i dont know what. Guess it somehow generateing at front-end, but i actualy cant understand where and how. It is only one problem.
Он генерирует её где-то тут... /games/UnblockPuzzle/, но пока не понял каким образом. Так же попробовал изменить несколько цифр в числе и все равно получил ответ 200
There are a base64 ciphered number passes for claim reward. One part is accountId, and other part i dont know what. Guess it somehow generateing at front-end, but i actualy cant understand where and how. It is only one problem.
Он генерирует её где-то тут... /games/UnblockPuzzle/, но пока не понял каким образом. Так же попробовал изменить несколько цифр в числе и все равно получил ответ 200
Да я был там, залип на строчке eval(Zstd_DecStr()) со тсрокой зашифрованой. Оказалось там чисто код, который инициализирует движок игры. Даже через чатГпт пропустил - ничего интересного не нашел. Правда хз нафига шифровать это было...
Да я был там, залип на строчке eval(Zstd_DecStr()) со тсрокой зашифрованой. Оказалось там чисто код, который инициализирует движок игры. Даже через чатГпт пропустил - ничего интересного не нашел. Правда хз нафига шифровать это было...
А я застрял на функции
function OnGameFinished(e) {
parent.postMessage(JSON.stringify({
method: "OnGameFinished",
cipher: e
}))
}
Никак не пойму, откуда она вызывается. Но, боюсь, я слишком плох в js, нужно чтобы сюда посмотрел человек, который действительно разбирается
Складывается впечатление, что это число генерируется на основе низкоуровневых чисел связанных с количеством выделенной движку памяти (heap). ИЛи что то в этом роде. Но в таком случае как сервак это проверяет? И проверяет ли?
и все равно получил ответ 200
стоп. Так оно ж работает. А зачем ты дальше паришся? Сейчас у меня кд откатился, я попробовал зарандомить значение между двумя значениями, которые записал при прохождении игры руками, и все ок. Резолвед.
let firstPart = await ut.getRandomInt(734792259, 883845570); //generate random value between two valid values
firstPart = "0" + firstPart //add zero, because it must be 10 digits string
let cipher = await btoa( firstPart + "|" + clickerUser.id) //to base64
Кароч авторы хомяка нас реально за хомяков держат, и на понт берут. Я уже всерьез собирался "ии" писать, который будет решение находить и через селениум "честно" проходить игру)
Вот полный код если че
async function playMiniGame(){
if (isMiniGameClaimed) { //get it from config
ut.log("Key already claimed")
return
}
if (nextMiniGameSeconds > 0) { //get it from config
ut.log("Key timer not ready " + nextMiniGameSeconds)
return
}
await startMiniGame()
await ut.sleep(ut.getRandomInt(20000, 29000)) //simulate game played
await finishMiniGame()
}
async function startMiniGame(){
await ut.log("Start minigame")
let resp = await fetch("https://api.hamsterkombatgame.io/clicker/start-keys-minigame", {
"headers": {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9",
"authorization": "Bearer " + accessToken,
"priority": "u=1, i",
"sec-ch-ua": "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Android WebView\";v=\"126\"",
"sec-ch-ua-mobile": "?1",
"sec-ch-ua-platform": "\"Android\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"x-requested-with": "org.telegram.messenger",
"Referer": "https://hamsterkombatgame.io/",
"Referrer-Policy": "strict-origin-when-cross-origin"
},
"body": null,
"method": "POST"
});
console.log(resp.status)
console.log(await resp.json())
}
async function finishMiniGame(){
await ut.log("Finish minigame")
// let cipher = await btoa(ut.getRandomInt(10) + "|" + clickerUser.id)
let firstPart = await ut.getRandomInt(734792259, 883845570);
firstPart = "0" + firstPart
let cipher = await btoa( firstPart + "|" + clickerUser.id)
console.log("Cipher = " + cipher)
let resp = await fetch("https://api.hamsterkombatgame.io/clicker/claim-daily-keys-minigame", {
"headers": {
"accept": "application/json",
"accept-language": "en-US,en;q=0.9",
"authorization": "Bearer " + accessToken,
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Android WebView\";v=\"126\"",
"sec-ch-ua-mobile": "?1",
"sec-ch-ua-platform": "\"Android\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"x-requested-with": "org.telegram.messenger",
"Referer": "https://hamsterkombatgame.io/",
"Referrer-Policy": "strict-origin-when-cross-origin"
},
// "body": "{\"cipher\":\"" + cipher +"\"}",
"body": JSON.stringify({cipher: cipher}),
"method": "POST"
});
console.log(resp.status)
console.log(await resp.json())
}
Так оно ж работает. А зачем ты дальше паришся?
Парюсь, чтобы не посылать рандомные значения. Мы же не знаем, проверяет ли серверная сторона их или нет. Не хочется всю ферму в бан отправить из-за одного числа =)
Так оно ж работает. А зачем ты дальше паришся?
Парюсь, чтобы не посылать рандомные значения. Мы же не знаем, проверяет ли серверная сторона их или нет. Не хочется всю ферму в бан отправить из-за одного числа =)
они бы сразу их не принимали. Вряд ли они их куда то записывают, чтобы потом сверить. Ну как знаешь)
Так оно ж работает. А зачем ты дальше паришся?
Парюсь, чтобы не посылать рандомные значения. Мы же не знаем, проверяет ли серверная сторона их или нет. Не хочется всю ферму в бан отправить из-за одного числа =)
они бы сразу их не принимали. Вряд ли они их куда то записывают, чтобы потом сверить. Ну как знаешь)
Так не просто же так Valve делают "волновые" баны пользователей. Даёшь возможность бото-пользователям расшириться и потом массово их банишь. Ибо если банить сразу, то крайне мало людей попадутся.
К слову. Я немного копнул.
Поэтапно.
Мини-игра запускается [POST] + body: null /clicker/start-keys-minigame
Успешное окончание игры [POST] + body: JSON.stringify({ cipher: btoa(${cipher}|${account_id}
) }) /clicker/claim-daily-keys-minigame
btoa(value) = base64encode(value)
Что за cipher пока неизвестно.
Знаю, что по началу игры подгружается страничка: https://hamsterkombatgame.io/games/UnblockPuzzle/?v
На ней есть такой кусочек кода:
function OnGameFinished(e) {
parent.postMessage(JSON.stringify({
method: "OnGameFinished",
cipher: e
}))
}
Но я не нашёл что вызывает эту функцию, чтобы найти откуда берётся аргумент для неё.
Тоже не могу найти где идёт вызов...
Такое ощущение что они используют WASM для какой-то части игры, тем более в том же самом эндпоинте
https://hamsterkombatgame.io/games/UnblockPuzzle/?v
в самом конце какие-то зашифрованные файлы массивом лежат в переменной ARCHIVEJS_DATA
, попробую поискать где они расшифровываются
Да и консоль твердит об их расшифровке
archive/archive_files.json: decode 3 ms, onload() 1 ms
UnblockPuzzle/:11 archive/game0.projectc: decode 9 ms, onload() 0 ms
UnblockPuzzle/:11 archive/game0.arci: decode 1 ms, onload() 0 ms
UnblockPuzzle/:11 DefoldGames_wasm.js: decode 21 ms, onload() 10 ms
UnblockPuzzle/:11 archive/game0.arcd: decode 2000 ms, onload() 1 ms
UnblockPuzzle/:11 archive/game0.dmanifest: decode 990 ms, onload() 0 ms
UnblockPuzzle/:11 archive/game0.public.der: decode 1000 ms, onload() 1 ms
UnblockPuzzle/:11 DefoldGames.wasm: decode 14567 ms, onload() 1 ms
Да просто выложите запрос https://api.hamsterkombatgame.io/clicker/claim-daily-keys-minigame с валидными данными {"cipher": "_тутbase64"}
Да и консоль твердит об их расшифровке
archive/archive_files.json: decode 3 ms, onload() 1 ms UnblockPuzzle/:11 archive/game0.projectc: decode 9 ms, onload() 0 ms UnblockPuzzle/:11 archive/game0.arci: decode 1 ms, onload() 0 ms UnblockPuzzle/:11 DefoldGames_wasm.js: decode 21 ms, onload() 10 ms UnblockPuzzle/:11 archive/game0.arcd: decode 2000 ms, onload() 1 ms UnblockPuzzle/:11 archive/game0.dmanifest: decode 990 ms, onload() 0 ms UnblockPuzzle/:11 archive/game0.public.der: decode 1000 ms, onload() 1 ms UnblockPuzzle/:11 DefoldGames.wasm: decode 14567 ms, onload() 1 ms
там ничего не шифровано, просто сжато fzstd
That's how it works. And why are you bothering further?
I worry so as not to send random values. We don't know if the server side checks them or not. I don't want to ban the whole farm because of one number =)
They wouldn't accept them right away. It is unlikely that they write them down somewhere in order to check them later. Well, as you know)
So it's not for nothing that Valve makes "wave" bans of users. You give bot users the opportunity to expand and then ban them en masse. Because if you ban immediately, then very few people will get caught.
By the way. I did a little digging.
Gradually. Mini-game starts [POST] + body: null Successful end of the game [POST] + body: JSON.stringify({ cipher: btoa(
/clicker/start-keys-minigame``${cipher}|${account_id}
) })/clicker/claim-daily-keys-minigame
btoa(value) = base64encode(value)
What kind of cipher is still unknown. I know that at the beginning of the game, the page loads: It has this piece of code:
https://hamsterkombatgame.io/games/UnblockPuzzle/?v
function OnGameFinished(e) { parent.postMessage(JSON.stringify({ method: "OnGameFinished", cipher: e })) }
But I haven't found what calls this function to find where the argument for it comes from.
called on this js: https://hamsterkombatgame.io/_nuxt/default.CXYCtMmd.js
Search/Find this function: function r(c)
function r(c) {
try {
const s = JSON.parse(c.data);
if (s.method)
switch (s.method) {
case "OnGameFinished":
t.cipher = window.btoa(s.cipher + "|" + Ke().account_id),
t.resetMatchMiniGameInterval(),
i();
break;
case "OnSceneLoaded":
t.sceneLoaded = !0;
break
}
} catch {}
}
you all need to write a lot of Tampermonkey scripts to log what c is. especially c.data
Hopefully, someone figures it out
Так не просто же так Valve делают "волновые" баны пользователей. Даёшь возможность бото-пользователям расшириться и потом массово их банишь. Ибо если банить сразу, то крайне мало людей попадутся.
в таком случае, эти данные должны как то передавать между сервером и клиентом, чтобы они могли на сервере сравнить правильность шифра, разве нет? Я искал, но не нашел ничего похожего. Правда только по XHR запросам прошелся.
вызванный на этом js: https://hamsterkombatgame.io/_nuxt/default.CXYCtMmd.js
Очень похоже, что по вашей ссылке находится приёмник этого сообщения
Да просто выложите запрос
У меня таки получилось решить сегодняшнее (после нескольких затупов): Вышел такой шифр: MDYzOTU0NjU4MXwxNDU0NjM2MTA2
После расшифровки base64: 0639546581|1454636106
1454636106 - действительно мой id, но самое интересное, что 639546581 не особо подходит под getRandomInt(734792259, 883845570)
Да просто выложите запрос
У меня таки получилось решить сегодняшнее (после нескольких затупов): Вышел такой шифр: MDYzOTU0NjU4MXwxNDU0NjM2MTA2
После расшифровки base64: 0639546581|1454636106
1454636106 - действительно мой id, но самое интересное, что 639546581 не особо подходит под getRandomInt(734792259, 883845570)
Да, там более широкий диапазон. У меня выходило число начинающееся с единицы когда руками делал, но то значение не сохранил. А для рандома взял два значения которые у меня были записаны. Это не значит что это весь предел возможных значений
============
Я в своих раскопках по стактрейсу дошел до этой строки. Файл называется VM120 - я так понимаю это какой динамически создаваемый файл с кодом
С этой строки вызывается файл VM153 с только одной строкой, где уже есть готовая первая часть шифра
OnGameFinished('0976422942')
это уже вызов метода из UnblockPuzzle.
Хотя я и не понимаю как он может вызываться с полностью пустого файла без импортов.
Если идти глубже VM120 - то там начинается вообще какая то дичь, не похожая на джаваскрипт.
Да просто выложите запрос
У меня таки получилось решить сегодняшнее (после нескольких затупов): Вышел такой шифр: MDYzOTU0NjU4MXwxNDU0NjM2MTA2 После расшифровки base64: 0639546581|1454636106 1454636106 - действительно мой id, но самое интересное, что 639546581 не особо подходит под getRandomInt(734792259, 883845570)
Да, там более широкий диапазон. У меня выходило число начинающееся с единицы когда руками делал, но то значение не сохранил. А для рандома взял два значения которые у меня были записаны. Это не значит что это весь предел возможных значений
============
Я в своих раскопках по стактрейсу дошел до этой строки. Файл называется VM120 - я так понимаю это какой динамически создаваемый файл с кодом
С этой строки вызывается файл VM153 с только одной строкой, где уже есть готовая первая часть шифра
OnGameFinished('0976422942')
это уже вызов метода из UnblockPuzzle. Хотя я и не понимаю как он может вызываться с полностью пустого файла без импортов.Если идти глубже VM120 - то там начинается вообще какая то дичь, не похожая на джаваскрипт.
OnGameFinished('0976422942') передастся из webasm (sdk defold) в виде utf8 строки и eval просто ее выполняет. собственно на константу 235305 есть ссылка внутри wasm. 0976422942 - очевидно ид игры. Но почему-то важно, что бы он начинался с 0...
/config теперь содержит levelConfig. например - a - - c -.- a b b c -.0 0 e - c -.f - e d d v.f g g x - v.f h h x z z
Если перевести его в таблицу - это будет начальное положение фигур.
- a - - c -
- a b b c -
0 0 e - c -
f - e d d v
f g g x - v
f h h x z z
Да просто выложите запрос
У меня таки получилось решить сегодняшнее (после нескольких затупов): Вышел такой шифр: MDYzOTU0NjU4MXwxNDU0NjM2MTA2 После расшифровки base64: 0639546581|1454636106 1454636106 - действительно мой id, но самое интересное, что 639546581 не особо подходит под getRandomInt(734792259, 883845570)
Да, там более широкий диапазон. У меня выходило число начинающееся с единицы когда руками делал, но то значение не сохранил. А для рандома взял два значения которые у меня были записаны. Это не значит что это весь предел возможных значений
============
Я в своих раскопках по стактрейсу дошел до этой строки. Файл называется VM120 - я так понимаю это какой динамически создаваемый файл с кодом
С этой строки вызывается файл VM153 с только одной строкой, где уже есть готовая первая часть шифра
OnGameFinished('0976422942')
это уже вызов метода из UnblockPuzzle. Хотя я и не понимаю как он может вызываться с полностью пустого файла без импортов.Если идти глубже VM120 - то там начинается вообще какая то дичь, не похожая на джаваскрипт.
Тоже до туда дошел и дальше не продвинулся
Я предполагаю, что это число генерируется на основе расположения свечей
Cmmon!! Do lots of scripts local overrides on the devtools with lots of console.log
Но почему-то важно, что бы он начинался с 0...
Разве все числа начинаются с 0?
Но почему-то важно, что бы он начинался с 0
нет, бывают и с единицы
config теперь содержит levelConfig. например - a - - c -.- a b b c -.0 0 e - c -.f - e d d v.f g g x - v.f h h x z z Если перевести его в таблицу - это будет начальное положение фигур.
Да, на это я первым делом обратил внимание, даже рисовал карты фигур на момент прохождения игры, думал может они как то в цифры трансформируются. но нет. Дрочился несколько часов с этим, чатГпт тоже дрючил.
Я предполагаю, что это число генерируется на основе расположения свечей
Тогда бы оно было бы у всех одинаковое. Если туда не примешивается что-то ещё
I assume that this number is generated based on the location of the candles
Then it would be the same for everyone. If something else is not mixed in there
Different ways to solve it and get the key through. Hence, different candle positions
Not sure but i feel like the first part has something to do with remaining time before losing the game
Nice idea, but as i see, hamster developers not so smart) //upd: or so? Need to check,
Different ways to solve it and get the key through. Hence, different candle positions
there are not many variations of it. So it must repeat sometimes. If we gather here our first parts of cipher, we can check this. On yesterday game i checked 4 ciphers, it not repeated at all,
We found the logic behind and tested it on two accounts, it worked without any issues, feel free to look at it and test if it works for you too. Commit Link
We found the logic behind and tested it on two accounts, it worked without any issues, feel free to look at it and test if it works for you too. Commit Link
cipher = (
("0" + str(waitTime) + str(random.randint(10000000000, 99999999999)))[:10]
+ "|"
+ str(AccountID)
)
Но это же рандом...
We found the logic behind and tested it on two accounts, it worked without any issues, feel free to look at it and test if it works for you too. Commit Link
cipher = ( ("0" + str(waitTime) + str(random.randint(10000000000, 99999999999)))[:10] + "|" + str(AccountID) )
But this is random...
First digits are not, they're based on waitTime, rest are random yes, but it works.
We found the logic behind and tested it on two accounts, it worked without any issues, feel free to look at it and test if it works for you too. Commit Link
This is a bad idea to use random numbers. This might cause a ban of all bot users. Yes, currently there are no bans. But they absolutely can collect data and then make a mass ban or prohibit withdrawal.
So we are looking for numbers in first part of cipher.
I found that this number is known as game starts:
{"method":"StartGame","level":"- b - c d d.a b - c…e -.h - f z z v.h x x - - v","number":1721505600}
caller function:
function onSandboxMessage(e) {
let a = "string" == typeof e.data ? JSON.parse(e.data) : e.data;
a && "StartGame" === a.method && JsToDef.send("__StartGame", a)
}
So it should be used in request.
The goal is to find where this number comes from.
We found the logic behind and tested it on two accounts, it worked without any issues, feel free to look at it and test if it works for you too. Commit Link
cipher = ( ("0" + str(waitTime) + str(random.randint(10000000000, 99999999999)))[:10] + "|" + str(AccountID) )
But this is random...
First digits are not, they're based on waitTime, rest are random yes, but it works.
random works by now. Even without time. Look my code above. But people fear, that it can be banned in future, if cipher will be not accurate. ACtualy this is the only problem.
We found the logic behind and tested it on two accounts, it worked without any issues, feel free to look at it and test if it works for you too. Commit Link
cipher = ( ("0" + str(waitTime) + str(random.randint(10000000000, 99999999999)))[:10] + "|" + str(AccountID) )
But this is random...
First digits are not, they're based on waitTime, rest are random yes, but it works.
random works by now. Even without time. Look my code above. But people fear, that it can be banned in future, if cipher will be not accurate. ACtualy this is the only problem.
We tried random before, it didn't work actually ~ this new method which is generating the first digits based on remaining time is working much better. Having pure randomness didn't work (your code)
We tried random before, it didn't work actually ~ this new method which is generating the first digits based on remaining time is working much better. Having pure randomness didn't work (your code)
hmm. I tried it right now on a new game. It worked for me again.
If you got multiple accounts, try to play normally and look at the first parameter of cipher code (before |AccountID
), the longer your game takes, the lower the number is.
If you got multiple accounts, try to play normally and look at the first parameter of cipher code (before
|AccountID
), the longer your game takes, the lower the number is.
If I am not wrong, then cipher is known at the game beginning.
number - это число полученное переводом даты старта игры (startDate), которая находится в объекте dailyKeysMiniGame, в таймстемп и поделенная на 1e3
Причем startDate статична и сегодня она "2024-07-20T20:00:00.000Z" - 1721505600000
1721505600000 / 1e3 = 1721505600
Завтра получается будет "2024-07-21T20:00:00.000Z" - 1721592000000
1721592000000 / 1e3 = 1721592000
If you got multiple accounts, try to play normally and look at the first parameter of cipher code (before
|AccountID
), the longer your game takes, the lower the number is.
cant approve it. Checking it just now, with video recording. 18sec left = 0571102420 21sec left = 0006424190
but yesterday numbers (from my code) 0734792259 and 0883845570 all was finished when less then 10 seconds was left. And both are bigger then today numbers.
MDU3MTEwMjQyMHw2MDM5MTgwODUw 18 sec
MDAwNjQyNDE5MHwyODMwODY3NTc= 21 sec
https://hamsterkombatgame.io/games/UnblockPuzzle/?v
Это можно прописать в консоль, чтобы локально бесплатно собрать пазл:
window.postMessage({"method":"StartGame","level":"- b - c d d.a b - c e -.a b 0 0 e -.g g f - e -.h - f z z v.h x x - - v","number":1721505600});
А это, чтобы отдебажить сообщения:
window.addEventListener("message", (event) => {
if(event?.data?.hello) return;
console.log("Message received:", event);
console.log("Data:", event.data);
console.log("Origin:", event.origin);
console.log("Source:", event.source);
}, false);
number != cipher
А как ты вызвал OnGameFinished?
А как ты вызвал OnGameFinished?
Завершил пазл.
Для простоты пустой пазл с ключом: - - - - - -.0 0 - - - -.- - - - - -.- - - - - -.- - - - - -
cant approve it. Checking it just now, with video recording. 18sec left = 0571102420 21sec left = 0006424190
but yesterday numbers (from my code) 0734792259 and 0883845570 all was finished when less then 10 seconds was left. And both are bigger then today numbers.
Yes because today's time is 35 seconds.
Ну судя по тому, что мне всё время разные числа в cipher
попадаются (с одинаковым number
на входе), ощущение, будто они действительно случайные в каком-то диапазоне...
Завершил пазл.
Поиграл несколько раз, получил занимательные значения:
0612291910 0663935312 0763739863 0828511880 0218262748 0107944192 0641902159 0103925728
Генерятся рандомные числа. Никак это не проверяется. Нас снова всех надули - расходимся)
Завершил пазл.
Поиграл несколько раз, получил занимательные значения:
0612291910 0663935312 0763739863 0828511880 0218262748 0107944192 0641902159 0103925728
Генерятся рандомные числа. Никак это не проверяется. Нас снова всех надули - расходимся)
Разве что я бы перебдел и набрал массив этих чисел на случай, если они рандомные из списка.
Завершил пазл.
Поиграл несколько раз, получил занимательные значения:
0612291910 0663935312 0763739863 0828511880 0218262748 0107944192 0641902159 0103925728
Генерятся рандомные числа. Никак это не проверяется. Нас снова всех надули - расходимся)
Согласен. Я думаю нас на понт берут и расчитывают что мы будем бояться рандом юзать.
Разве что я бы перебдел и набрал массив этих чисел на случай, если они рандомные из списка.
А если они рандомные, а у тебя будут постоянно одни и те же числа? В ногу себе выстрелишь.
===========
Разрабы хомяка в принципе не отличаются каким то интересом к безопаности, в отличии от тапсвапа например. У меня initData работают еще те, которые я больше месяца назад закинул, тогда как например в Musk Empire они меняются каждый час. Так же свободно передают дельту апгрейдов, что максимально упрощает высчитываение самых выгодных апгрейдов. одним словом - распиздяи (прям как я)).
почему вы думаете что это рандом, а не хэш-функция с солью в виде tg_id? не просто же так его добавляют к строке
почему вы думаете что это рандом, а не хэш-функция с солью в виде tg_id? не просто же так его добавляют к строке
я думаю, потому что рандом работает) но я открыт к аргументам. Потому я все еще мониторю это обсуждение и пока не запускаю скрипт на все аккаунты.
Это может быть хэш функция текущего времени, чтобы потом проверить дельту между "собрана в мини игре" и "зарегистрировано что собрано", но блин слишком сильный разброс значений
minigame was added to HamsterKombat, please add autosolver for if